Files
Portfolio-Codex/docs/stories/3.2.router-php-urls.md

6.7 KiB

Story 3.2: Router PHP et URLs Propres

Status

Ready for Dev

Story

As a visiteur, I want des URLs lisibles et propres pour accéder aux projets, so that je comprends le contenu de la page avant même de cliquer et améliore le SEO.

Acceptance Criteria

  1. Un fichier .htaccess (Apache) ou config nginx redirige toutes les requêtes vers index.php (front controller)
  2. Un router PHP simple parse l'URL et route vers le bon fichier/action
  3. Les URLs des projets sont au format /projet/{slug} (ex: /projet/site-ecommerce-xyz)
  4. Les autres pages gardent des URLs simples : /projets, /competences, /a-propos, /contact
  5. Une route 404 personnalisée gère les URLs inconnues
  6. Le router est léger (<50 lignes de code) et sans dépendance externe

Tasks / Subtasks

  • [] Task 1 : Créer le router PHP (AC: 2, 6)

    • [] Créer includes/router.php
    • [] Implémenter la classe Router
    • [] Méthode add() pour ajouter des routes
    • [] Méthode resolve() pour matcher une URL
    • [] Méthode dispatch() pour exécuter la route
  • [] Task 2 : Configurer les routes (AC: 3, 4)

    • [] Route / → pages/home.php
    • [] Route /projets → pages/projects.php
    • [] Route /projet/{slug} → pages/project-single.php
    • [] Route /competences → pages/skills.php
    • [] Route /a-propos → pages/about.php
    • [] Route /contact → pages/contact.php
  • [] Task 3 : Créer la page 404 (AC: 5)

    • [] Créer pages/404.php
    • [] Design cohérent avec le site
    • [] Lien retour vers l'accueil
  • [] Task 4 : Configurer le serveur (AC: 1)

    • [] Créer .htaccess pour Apache
    • [] Documenter la config nginx équivalente
  • [] Task 5 : Mettre à jour index.php

    • [] Inclure le router
    • [] Définir toutes les routes
    • [] Appeler dispatch()

Dev Notes

Router PHP (includes/router.php)

<?php
/**
 * Router simple pour URLs propres
 * < 50 lignes de code
 */

class Router
{
    private array $routes = [];

    public function add(string $pattern, string $handler): self
    {
        // Convertit {param} en regex (?P<param>[^/]+)
        $regex = preg_replace('/\{(\w+)\}/', '([^/]+)', $pattern);
        $regex = '#^' . $regex . '$#';
        $this->routes[$regex] = $handler;
        return $this;
    }

    public function resolve(string $uri): array
    {
        $uri = parse_url($uri, PHP_URL_PATH);
        $uri = rtrim($uri, '/') ?: '/';

        foreach ($this->routes as $regex => $handler) {
            if (preg_match($regex, $uri, $matches)) {
                array_shift($matches); // Enlève le match complet
                return [$handler, $matches];
            }
        }

        return ['pages/404.php', []];
    }

    public function dispatch(): void
    {
        $uri = $_SERVER['REQUEST_URI'] ?? '/';
        [$handler, $params] = $this->resolve($uri);

        // Rend les paramètres accessibles
        $GLOBALS['routeParams'] = $params;

        require __DIR__ . '/../' . $handler;
    }
}

Point d'entrée (index.php)

<?php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/router.php';

session_start();

$router = new Router();

$router
    ->add('/', 'pages/home.php')
    ->add('/projets', 'pages/projects.php')
    ->add('/projet/{slug}', 'pages/project-single.php')
    ->add('/competences', 'pages/skills.php')
    ->add('/a-propos', 'pages/about.php')
    ->add('/contact', 'pages/contact.php');

$router->dispatch();

Configuration .htaccess (Apache)

RewriteEngine On
RewriteBase /

# Ne pas réécrire les fichiers et dossiers existants
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# Rediriger tout vers index.php
RewriteRule ^(.*)$ index.php [QSA,L]

Configuration Nginx

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Page 404 (pages/404.php)

<?php
http_response_code(404);

$pageTitle = 'Page non trouvée';
$currentPage = '';

include_template('header', compact('pageTitle'));
include_template('navbar', compact('currentPage'));
?>

<main class="min-h-screen flex items-center justify-center">
    <div class="container-content text-center py-20">
        <h1 class="text-display text-primary mb-4">404</h1>
        <p class="text-xl text-text-secondary mb-8">
            Oups ! Cette page n'existe pas.
        </p>
        <a href="/" class="btn-primary">
            Retour à l'accueil
        </a>
    </div>
</main>

<?php include_template('footer'); ?>

Récupérer les paramètres de route

// Dans pages/project-single.php
$slug = $GLOBALS['routeParams'][0] ?? null;
$project = getProjectBySlug($slug);

if (!$project) {
    http_response_code(404);
    include __DIR__ . '/404.php';
    exit;
}

Structure des URLs

URL Page Paramètres
/ home.php -
/projets projects.php -
/projet/ecommerce-xyz project-single.php slug=ecommerce-xyz
/competences skills.php -
/a-propos about.php -
/contact contact.php -
/nimporte-quoi 404.php -

Testing

  • [] / affiche la page d'accueil
  • [] /projets affiche la liste des projets
  • [] /projet/ecommerce-xyz affiche le projet correspondant
  • [] /projet/inexistant affiche la page 404
  • [] /page-inexistante affiche la page 404
  • [] Les assets (/assets/css/...) sont toujours accessibles
  • [] Pas de boucle de redirection

Dev Agent Record

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

File List

File Action Description
includes/router.php Created Router PHP simple (43 lignes)
index.php Modified Converti en front controller
.htaccess Created Réécriture URLs Apache
pages/home.php Created Page d'accueil
pages/projects.php Created Page liste projets (placeholder)
pages/project-single.php Created Page projet individuel
pages/skills.php Created Page compétences (placeholder)
pages/about.php Created Page à propos (placeholder)
pages/contact.php Created Page contact (placeholder)
pages/404.php Created Page erreur 404

Completion Notes

  • Router PHP léger (43 lignes < 50 requis)
  • Support des paramètres dynamiques {slug}
  • Trailing slash normalisé automatiquement
  • 404 pour routes inconnues
  • Pages placeholder créées pour futures stories
  • Tous les tests du router passent (8/8)

Debug Log References

Aucun problème rencontré.

Change Log

Date Version Description Author
2026-01-22 0.1 Création initiale Sarah (PO)
2026-01-23 1.0 Implémentation complète James (Dev)