Concevez toutes les routes pour consulter, créer, modifier et supprimer les livres, puis vérifiez leurs contrats HTTP.
Carte cible
Sept routes, une ressource cohérente.
/booksindex200/books/createcreate200/booksstore201 / 302/books/{id}show200 / 404/books/{id}/editedit200 / 404/books/{id}update200 / 302/books/{id}destroy204 / 302Une route est un contrat
Une route ne signifie pas seulement « cette URL ouvre cette page ». Elle définit un contrat entre le client et l’application : une méthode, un chemin, des paramètres, des protections et une action.
GET /books promet de consulter la collection. POST /books promet de traiter une création. Ils partagent le même chemin, mais leurs intentions et leurs réponses sont différentes.
Une route doit permettre de comprendre l’interface HTTP sans lire le contrôleur. Une requête SQL, du HTML ou de longues conditions y mélangeraient des responsabilités.
Prononcez toujours la méthode et le chemin : GET /books, pas seulement /books.
Route::get('/books', [BookController::class, 'index']);Choisir la méthode HTTP
La méthode annonce l’effet attendu. GET consulte, POST crée ou déclenche, PATCH modifie une partie et DELETE supprime.
GET doit rester sans effet métier volontaire : actualiser la page ne doit jamais créer un second livre. Navigateurs, caches et robots dépendent de cette propriété.
POST n’est pas un GET plus puissant. PATCH /books/42 cible une ressource existante; DELETE /books/42 exprime clairement sa suppression.
Une action déclenchée par un lien normal doit généralement être GET et ne jamais supprimer.
Concevoir les URL
Une URL décrit une ressource, pas l’implémentation PHP. Préférez /books/42 à /showBook.php?id=42.
Utilisez des noms pluriels cohérents pour les collections. L’identifiant sélectionne un membre. Un sous-chemin représente une page ou une relation lorsque la méthode seule ne suffit pas.
Une bonne URL reste stable si BookController change, si SQLite devient MongoDB ou si la vue classique devient AML View.
Évitez les verbes techniques lorsque la méthode HTTP exprime déjà l’action.
Paramètres dynamiques et contraintes
Dans /books/{id}, id est extrait du chemin. Le routeur sait que /books/42 peut correspondre, mais vous devez encore définir les valeurs acceptables.
Une contrainte numérique empêche /books/bonjour d’atteindre un contrôleur qui attend un entier. Elle ne remplace pas la validation métier : 42 peut être bien formé mais désigner un livre absent.
Distinguez route non trouvée, paramètre invalide et ressource absente. Cette précision améliore statuts, journaux et tests.
Validez la forme tôt, puis l’existence et les règles dans la couche appropriée.
Route::get('/books/{id}', [BookController::class, 'show'])
->whereNumber('id');Routes nommées et URL
Une route nommée fournit un identifiant stable comme books.show. La vue génère alors le lien sans recopier /books/{id}.
Si le chemin devient /library/books/{id}, tous les appels par nom survivent après une seule modification.
Le nom décrit la destination applicative et suit une convention régulière resource.action, indépendante du texte visible.
Une route nommée centralise un contrat qui serait autrement recopié dans plusieurs vues.
$url = route('books.show', ['id' => $book->id]);Groupes, préfixes et middlewares
Un groupe applique une propriété commune : préfixe /admin, middleware auth ou espace de noms.
Regrouper évite de répéter les protections et rend la politique visible. Toutes les routes d’administration peuvent exiger une session; l’édition peut demander un rôle supplémentaire.
L’ordre des middlewares importe. Les en-têtes de sécurité doivent également couvrir les réponses anticipées 401, 403 et 429.
Déclarez une protection commune au niveau commun le plus proche, sans la rendre invisible.
Route::prefix('/admin')
->middleware(['auth', 'role:editor'])
->group(function (): void {
Route::patch('/books/{id}', [BookController::class, 'update']);
Route::delete('/books/{id}', [BookController::class, 'destroy']);
});Séparer web et API
Web et API peuvent partager les modèles, mais leurs contrats de réponse diffèrent.
Le web utilise souvent sessions, CSRF, redirections et HTML. Une API utilise JSON, jetons, statuts explicites et un préfixe versionné comme /api/v1.
Partagez les règles métier par les modèles ou services, puis laissez chaque surface adapter Request et Response à son client.
Partager le métier ne signifie pas mélanger les contrats HTTP du navigateur et de l’API.
routes/webapp.phproutes/api.phpOrganiser les fichiers
Un petit projet tient dans routes/webapp.php. Un fichier de plusieurs centaines de lignes devient une carte illisible.
Regroupez par surface ou fonctionnalité : webapp.php, api.php, admin.php, puis MovieRoute lorsque l’API adopte une route par contrôleur.
Le chargement doit rester découvrable. Évitez une magie qui parcourt tous les fichiers sans convention explicite.
Créez un fichier pour clarifier une frontière réelle, pas pour remplir des dossiers vides.
Distinguer 404 et 405
404 indique qu’aucune route ou ressource correspondante n’existe. 405 indique que le chemin existe, mais pas pour la méthode reçue.
GET /books/42 peut produire 404 si le livre est absent. POST /books/42 produit 405 si seules GET, PATCH et DELETE sont autorisées.
Cette distinction aide le client à corriger sa requête. Une réponse 405 peut annoncer les méthodes acceptées avec Allow.
Diagnostiquez le couple méthode-chemin avant l’existence de la ressource.
Tester la carte
Une route n’est terminée que lorsque son contrat est vérifié : succès, mauvaise méthode, paramètre invalide, ressource absente et protection middleware.
Observez les statuts, en-têtes, redirections et contenus essentiels sans lier les tests à des détails visuels inutiles.
Vérifiez aussi qu’une route sensible reste inaccessible sans authentification. Une bonne action avec le mauvais middleware reste une faille.
Chaque branche importante du contrat HTTP mérite un test reproductible.
Assemblage
La carte CRUD complète.
Lisez-la comme une table des matières HTTP. Chaque déclaration doit conduire à une méthode de contrôleur courte et précise.
Route::get('/books', [BookController::class, 'index'])->name('books.index');
Route::get('/books/create', [BookController::class, 'create'])->name('books.create');
Route::post('/books', [BookController::class, 'store'])->name('books.store');
Route::get('/books/{id}', [BookController::class, 'show'])->name('books.show');
Route::get('/books/{id}/edit', [BookController::class, 'edit'])->name('books.edit');
Route::patch('/books/{id}', [BookController::class, 'update'])->name('books.update');
Route::delete('/books/{id}', [BookController::class, 'destroy'])->name('books.destroy');Projet final
Construisez et testez BookRoute.
- Écrivez les sept routes CRUD sans logique métier.
- Nommez-les avec books.action.
- Contraignez id à un entier positif.
- Protégez création, modification et suppression.
- Ajoutez la version JSON /api/v1/books.
- Testez succès, 404, 405, validation et accès refusé.
Correction raisonnée
Vérifiez les contrats, pas seulement la syntaxe.
La qualité dépend des frontières : aucune requête de base dans routes, aucun secret, méthodes cohérentes, noms stables, contraintes explicites et protections visibles.
Pourquoi /books/create si POST /books crée ?
GET /books/create affiche le formulaire sans modifier les données. POST /books traite ensuite sa soumission.
Faut-il une route par contrôleur ?
Pour une API moyenne, MovieRoute avec MovieController est lisible. Pour un petit site, webapp.php suffit. Choisissez ce qui facilite la découverte sans créer de dossiers vides.