Controllers
Bloc Laravel · séance 2 · ~55 min
Au chapitre Routing, les routes /livres et /livres/{id} appelaient chacune une fonction anonyme écrite dans routes/web.php, qui renvoyait une phrase. Dans ce chapitre, vous déplacez le code de ces fonctions dans une classe, BookController, que vous créez avec Artisan. Vous y ajoutez ensuite des méthodes qui lisent la requête et renvoient une page, une redirection ou des données au format JSON, un texte structuré qu'un autre programme sait lire. Tout se fait dans premier-test-laravel, sans base de données.
À la fin de ce chapitre, vous serez capables de :
- créer un contrôleur avec Artisan et relier ses méthodes à des routes,
- écrire une méthode qui reçoit un paramètre de route ou la requête,
- choisir ce que renvoie une méthode (texte, vue, redirection ou JSON) et écrire son type de retour,
- créer un contrôleur à action unique et un contrôleur rangé dans un sous-dossier,
- diagnostiquer une classe introuvable, une méthode absente et un
returnoublié.
1. Des fonctions anonymes au contrôleur
Vous avez créé PostController au chapitre MVC avec la commande make:controller. Un contrôleur est une classe dont les méthodes répondent aux requêtes : pour chaque requête, Laravel appelle une de ces méthodes, puis fabrique la réponse à partir de ce qu'elle renvoie. Une route désigne la méthode à appeler avec un tableau, comme [PostController::class, 'index']. Au chapitre Routing, vous avez adopté une règle : dès que le code d'une route interroge un modèle ou prépare une vue avec des données, placez ce code dans un contrôleur.
Pour l'instant, votre routes/web.php contient les routes écrites pendant le chapitre Routing, sans la route de repli, puis, à la fin du fichier, celles de ses exercices. Les routes des livres y appellent encore des fonctions anonymes. La commande route:list le confirme : à droite de chaque route, elle indique le fichier et la ligne où la fonction est écrite.
php artisan route:list --path=livres
GET|HEAD livres .............................................. books.index › routes/web.php:16
POST livres ............................................................ routes/web.php:20
GET|HEAD livres/{id} .......................................... books.show › routes/web.php:24
Showing [3] routes
Dans ce chapitre, vous ajoutez aux livres une page de recherche, une page d'avis et une fiche en JSON. Si vous écrivez pour chacune une fonction de cinq ou dix lignes dans routes/web.php, le code des pages finit par cacher les adresses, et il devient difficile de voir d'un coup d'œil quelles adresses le site accepte. Vous allez donc rassembler le code des livres dans un seul fichier, app/Http/Controllers/BookController.php. Dans routes/web.php, chaque route des livres tiendra alors sur une ligne.
2. Créer un contrôleur avec Artisan
La commande et le fichier produit
À la racine du projet, lancez la commande qui crée le contrôleur :
php artisan make:controller BookController
INFO Controller [app/Http/Controllers/BookController.php] created successfully.
Artisan a écrit ce fichier :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BookController extends Controller
{
//
}
Vous avez déjà rencontré presque toutes ces lignes dans le bloc POO.
namespace App\Http\Controllers;correspond au dossierapp/Http/Controllers/. C'est la règle PSR-4, vue au chapitre L'atelier du bloc POO : quand PHP a besoin de la classeApp\Http\Controllers\BookController, le chargement automatique de Composer transforme ce nom en chemin et ouvreapp/Http/Controllers/BookController.php.use Illuminate\Http\Request;importe la classe qui représente une requête. Artisan l'ajoute d'avance, et elle servira à la section 4.extends Controllerfait hériterBookControllerde la classeController, écrite dansapp/Http/Controllers/Controller.php. Vous l'avez ouverte au chapitre Structure d'un projet Laravel : c'est une classe abstraite, et elle est vide. Laravel n'oblige pas un contrôleur à en hériter. Elle sert le jour où tous vos contrôleurs ont besoin d'une même méthode : vous l'écrivez une seule fois dansController, et chaque contrôleur en hérite.//marque l'endroit où vous écrirez les méthodes.
Déplacer les routes des livres
Remplacez le contenu de la classe par deux méthodes, qui renvoient les mêmes phrases que les fonctions anonymes du chapitre Routing :
class BookController extends Controller
{
public function index(): string
{
return 'Liste des livres';
}
public function show(string $id): string
{
return "Fiche du livre $id";
}
}
En haut de routes/web.php, ajoutez la ligne use App\Http\Controllers\BookController; juste au-dessus de celle qui importe PostController. Remplacez ensuite les routes GET /livres et GET /livres/{id} par les deux lignes ci-dessous. La route POST /livres, placée entre les deux, reste telle quelle.
use App\Http\Controllers\BookController;
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
// …
Route::get('/livres', [BookController::class, 'index'])->name('books.index');
Route::post('/livres', function () {
return 'Livre enregistré';
});
Route::get('/livres/{id}', [BookController::class, 'show'])->name('books.show');
Les routes gardent leur nom, books.index et books.show. Le template books/links.blade.php et la redirection /dernier-livre du chapitre Routing, qui utilisent ces noms, continuent donc de fonctionner.

Pour une requête GET /livres/12, Laravel parcourt routes/web.php et retient la route /livres/{id}. Il lit la classe et la méthode dans le tableau [BookController::class, 'show'], crée un objet BookController et appelle show() avec la valeur "12". Laravel place la phrase renvoyée par la méthode dans la réponse, et c'est elle que curl affiche :
curl -s http://premier-test-laravel.test/livres
curl -s http://premier-test-laravel.test/livres/12
Liste des livres
Fiche du livre 12
Les deux adresses renvoient les mêmes phrases qu'avant le déplacement. Ce qui change, c'est ce qu'affiche route:list :
php artisan route:list --path=livres
GET|HEAD livres ........................................... books.index › BookController@index
POST livres ............................................................ routes/web.php:19
GET|HEAD livres/{id} ........................................ books.show › BookController@show
Showing [3] routes
Pour les deux routes des livres, route:list indique maintenant la classe et la méthode appelées, BookController@index et BookController@show. La route POST /livres appelle toujours une fonction anonyme, et la liste indique donc toujours sa ligne dans routes/web.php, maintenant 19 au lieu de 20. L'import ajouté en haut du fichier a décalé cette route d'une ligne vers le bas, mais la route GET /livres, placée au-dessus, tient maintenant sur une ligne au lieu de trois.
3. Les paramètres de route dans une méthode
Une méthode de contrôleur reçoit les paramètres de route de la même façon qu'une fonction anonyme, comme au chapitre Routing. Laravel prend dans l'adresse les valeurs qui occupent la place des accolades, puis les donne aux paramètres de la méthode dans le même ordre. Le nom de vos variables ne joue aucun rôle.
Le numéro d'un livre est un nombre. Dans BookController, remplacez donc string $id par int $id :
public function show(int $id): string
{
return "Fiche du livre $id";
}
Avec l'adresse /livres/12, la page s'affiche toujours, car PHP convertit la chaîne "12" en entier au moment d'appeler show(). Avec /livres/abc, cette conversion est impossible. PHP lance alors une TypeError, l'exception qui signale une valeur du mauvais type, et Laravel renvoie une page d'erreur avec le code 500. Elle affiche ce message :
App\Http\Controllers\BookController::show(): Argument #1 ($id) must be of type int, string given
Le message nomme la classe et la méthode, puis le paramètre en cause, $id. Vous corrigez ce problème comme au chapitre Routing, avec une contrainte sur la route. Dans routes/web.php, ajoutez whereNumber('id') à la route books.show :
Route::get('/livres/{id}', [BookController::class, 'show'])->whereNumber('id')->name('books.show');
curl -s -i http://premier-test-laravel.test/livres/abc
HTTP/1.1 404 Not Found
…
Avec la contrainte, abc ne correspond plus à la route books.show. Laravel ne trouve aucune autre route pour cette adresse, et il répond 404 sans appeler show(). Quand une méthode déclare un paramètre int, ajoutez whereNumber à sa route.
4. Lire la requête
Recevoir Request $request
Au chapitre MVC, posts.php lisait le paramètre d'URL ?ordre=ancien dans le tableau $_GET. Dans un contrôleur, vous lisez ce type de valeur dans un objet de la classe Illuminate\Http\Request, celle que make:controller a importée. Cet objet contient tout ce que le navigateur a envoyé : le verbe, le chemin, les paramètres d'URL, et des informations techniques appelées en-têtes, que vous rencontrerez à la section 5.
Pour recevoir cet objet, ajoutez un paramètre Request $request à la méthode qui en a besoin. Avant d'appeler une méthode de contrôleur, Laravel regarde le type de chacun de ses paramètres. Quand un paramètre a le type Request, Laravel lui donne comme valeur l'objet qui représente la requête en cours. Ajoutez cette méthode à BookController :
public function search(Request $request): string
{
$word = $request->query('q', 'rien');
return "Recherche de « $word », requête {$request->method()} sur {$request->path()}";
}
$request->query('q', 'rien')renvoie la valeur du paramètre d'URLq. Si l'adresse ne contient pas?q=, elle renvoie le second argument,'rien'.$request->method()renvoie le verbe de la requête, iciGET.$request->path()renvoie le chemin de l'adresse demandée, sans la barre oblique du début, icilivres/recherche.
Dans une chaîne entre guillemets doubles, les accolades autour de $request->method() indiquent à PHP d'appeler la méthode et d'insérer dans la chaîne la valeur qu'elle renvoie.
Placer la route au bon endroit
L'adresse /livres/recherche correspond aussi à la route /livres/{id} : recherche pourrait très bien être la valeur de {id}. Au chapitre Routing, vous avez vu que Laravel garde alors la première des deux routes déclarées. Déclarez donc la route fixe, /livres/recherche, avant la route à paramètre. Ajoutez-la entre la route POST /livres et la route books.show :
Route::get('/livres/recherche', [BookController::class, 'search'])->name('books.search');
curl -s "http://premier-test-laravel.test/livres/recherche?q=Nothomb"
curl -s http://premier-test-laravel.test/livres/recherche
Recherche de « Nothomb », requête GET sur livres/recherche
Recherche de « rien », requête GET sur livres/recherche
Les guillemets autour de l'adresse sont nécessaires : sans eux, le terminal traite le ? comme un caractère spécial et ne transmet pas l'adresse telle quelle à curl.
Dans votre fichier, la contrainte whereNumber ajoutée à la section 3 suffirait déjà : recherche ne contient pas de chiffres, donc l'adresse ne correspond pas à /livres/{id}. L'ordre des routes devient décisif quand une route à paramètre n'a pas de contrainte. Si /livres/{id} passait en premier sans whereNumber, Laravel appellerait show() avec "recherche", et vous retrouveriez l'erreur 500 de la section 3.
La requête et un paramètre de route ensemble
Une méthode peut recevoir à la fois la requête et un paramètre de route. Imaginez une page qui affiche les avis sur un livre, à l'adresse /livres/12/avis. Le visiteur choisit l'ordre des avis en ajoutant à l'adresse le paramètre d'URL tri, par exemple ?tri=note. Ajoutez cette méthode à BookController :
public function reviews(Request $request, int $id): string
{
$sort = $request->query('tri', 'date');
return "Avis sur le livre $id, triés par $sort";
}
Ajoutez la route juste après la route books.show :
Route::get('/livres/{id}/avis', [BookController::class, 'reviews'])->whereNumber('id');
curl -s "http://premier-test-laravel.test/livres/12/avis?tri=note"
curl -s http://premier-test-laravel.test/livres/12/avis
Avis sur le livre 12, triés par note
Avis sur le livre 12, triés par date
Laravel repère le paramètre de type Request et lui donne la requête. Il donne ensuite la valeur 12, lue dans l'adresse, au paramètre qui reste, $id. Si vous écrivez reviews(int $id, Request $request), la réponse est la même, car Laravel repère toujours la requête par son type. La documentation de Laravel place la requête en premier, et ce cours suit cette habitude.
5. Ce que renvoie une méthode
Cinq retours, cinq réponses
Jusqu'ici, vos méthodes renvoient une chaîne. Une méthode peut renvoyer d'autres valeurs, et Laravel fabrique la réponse HTTP à partir de ce qu'elle renvoie.
Cette réponse commence par un code, comme 200 ou 302, suivi de lignes appelées en-têtes, qui décrivent la réponse. L'en-tête Content-Type annonce la nature du contenu : text/html pour une page, application/json pour des données JSON. L'en-tête Location donne l'adresse vers laquelle rediriger. L'option -i de curl affiche ce code et ces en-têtes.
| La méthode renvoie | Type de retour | Réponse |
|---|---|---|
"Fiche du livre $id" |
string |
200, Content-Type: text/html; charset=utf-8, la phrase |
view('books.links') |
View |
200, Content-Type: text/html; charset=utf-8, le HTML produit par le template |
redirect()->route('books.show', 12) |
RedirectResponse |
302, Location: http://premier-test-laravel.test/livres/12 |
response()->json(['id' => 12, 'title' => 'Livre 12']) |
JsonResponse |
200, Content-Type: application/json, {"id":12,"title":"Livre 12"} |
['id' => 12, 'title' => 'Livre 12'] |
array |
la même réponse que la ligne précédente |
string et array sont des types de PHP, utilisables sans import. Les trois autres sont des classes de Laravel. Une méthode qui en utilise une comme type de retour demande l'import de la classe en haut du contrôleur, comme View dans le PostController du chapitre MVC.
Voici ces trois imports. Ne les collez pas dans BookController maintenant : vous ajouterez chaque import au moment d'écrire la méthode qui en a besoin.
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
Un import écrit deux fois dans le même fichier arrête PHP avec ce message, et Laravel renvoie alors une erreur 500 pour toutes les routes du contrôleur :
Cannot use Illuminate\Http\RedirectResponse as RedirectResponse because the name is already in use

Renvoyer du JSON
Un texte JSON décrit des données avec des accolades, des clés entre guillemets et des valeurs. Un programme, par exemple une application mobile, lit ces données directement. Il n'a pas à les chercher au milieu des balises d'une page HTML, qui mélange les données et leur mise en forme. Ajoutez cette méthode à BookController :
public function data(int $id): array
{
return ['id' => $id, 'title' => "Livre $id", 'available' => true];
}
Ajoutez la route après celle des avis :
Route::get('/livres/{id}/donnees', [BookController::class, 'data'])->whereNumber('id');
curl -s -i http://premier-test-laravel.test/livres/12/donnees
HTTP/1.1 200 OK
…
Content-Type: application/json
…
{"id":12,"title":"Livre 12","available":true}
Laravel a converti le tableau PHP en texte JSON et a ajouté à la réponse l'en-tête Content-Type: application/json. Grâce à cet en-tête, le programme qui reçoit la réponse sait qu'il doit la lire comme du JSON, et non comme une page HTML. La conversion garde le type des valeurs : true reste un booléen et 12 un nombre, écrits sans guillemets, alors que le titre est une chaîne entre guillemets.
Rediriger avec un paramètre d'URL
Supposez que l'ancienne version du site proposait la recherche à l'adresse /chercher/Nothomb. Votre nouvelle page de recherche se trouve à /livres/recherche?q=Nothomb. Pour que les visiteurs qui ont gardé l'ancienne adresse dans leurs favoris arrivent quand même sur la bonne page, vous redirigez l'ancienne adresse vers la nouvelle. En haut de BookController, ajoutez l'import use Illuminate\Http\RedirectResponse; au-dessus de celui de Request, puis ajoutez cette méthode :
public function legacySearch(string $word): RedirectResponse
{
return redirect()->route('books.search', ['q' => $word]);
}
Ajoutez la route après celle des données :
Route::get('/chercher/{word}', [BookController::class, 'legacySearch']);
curl -s -I http://premier-test-laravel.test/chercher/Nothomb
HTTP/1.1 302 Found
…
Location: http://premier-test-laravel.test/livres/recherche?q=Nothomb
…
Le chemin de la route books.search ne contient aucun paramètre entre accolades. Quand vous donnez à route() une valeur dont le nom n'apparaît pas dans le chemin, Laravel l'ajoute à la fin de l'adresse comme paramètre d'URL, après un ?. Pour voir l'adresse produite sans passer par une redirection, affichez-la avec Tinker, que vous connaissez depuis le chapitre MVC :
php artisan tinker --execute="echo route('books.search', ['q' => 'Nothomb']);"
http://premier-test-laravel.test/livres/recherche?q=Nothomb
Le return oublié
Un oubli fréquent consiste à écrire la valeur sans return. Faites l'essai : dans index(), effacez le mot return pour ne garder que 'Liste des livres';, puis ouvrez /livres. La méthode déclare le type de retour : string mais ne renvoie rien. PHP lance donc une TypeError, et Laravel affiche une page d'erreur avec le code 500 :
App\Http\Controllers\BookController::index(): Return value must be of type string, none returned
Retirez maintenant aussi : string et rechargez la page. Cette fois, PHP ne signale rien : Laravel envoie une page vide avec le code 200, et aucun message ne vous indique quelle ligne chercher. Remettez le return et le type de retour. Écrivez le type de retour de chaque méthode : PHP vérifie alors la valeur renvoyée, et le message d'erreur nomme la méthode fautive.
6. Le contrôleur à action unique
Certaines adresses ne déclenchent qu'une seule action. Au chapitre Routing, par exemple, l'adresse /dernier-livre redirige le visiteur vers la fiche du livre 12, et rien d'autre. Pour ce code, une classe qui ne contient qu'une méthode est suffisante. Une telle classe s'appelle un contrôleur à action unique.
Dans un tel contrôleur, l'unique méthode porte un nom imposé, __invoke. Pour créer la classe avec cette méthode déjà écrite, ajoutez l'option --invokable à la commande :
php artisan make:controller LatestBookController --invokable
INFO Controller [app/Http/Controllers/LatestBookController.php] created successfully.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LatestBookController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
//
}
}
__invoke est une méthode magique, c'est-à-dire une méthode que PHP appelle lui-même dans une situation précise, comme __construct et __toString, vues au chapitre Objets et classes du bloc POO. PHP appelle __invoke quand vous utilisez un objet comme une fonction, en écrivant par exemple $controller(). Quand une route donne seulement le nom de la classe, Laravel complète l'action avec le nom __invoke, puis appelle cette méthode comme il appellerait show(). La route n'a donc pas besoin de nommer de méthode.
Le commentaire Handle the incoming request. signifie « traite la requête reçue ». Pour rediriger vers le livre 12, la méthode n'a pas besoin de lire la requête : vous pouvez retirer le paramètre Request $request et son import. Remplacez le contenu du fichier par ceci :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
class LatestBookController extends Controller
{
public function __invoke(): RedirectResponse
{
return redirect()->route('books.show', 12);
}
}
Dans routes/web.php, ajoutez l'import de LatestBookController juste au-dessus de celui de BookController, puis remplacez la route /dernier-livre et sa fonction anonyme par cette ligne. La route donne la classe seule, sans tableau ni nom de méthode :
Route::get('/dernier-livre', LatestBookController::class);
curl -s -I http://premier-test-laravel.test/dernier-livre
php artisan route:list --path=dernier
HTTP/1.1 302 Found
…
Location: http://premier-test-laravel.test/livres/12
…
GET|HEAD dernier-livre .............................................. LatestBookController
Showing [1] routes
La réponse n'a pas changé. route:list affiche le nom de la classe sans @ ni méthode, ce qui signale un contrôleur à action unique. Si la route a un paramètre, comme {id}, __invoke le reçoit comme n'importe quelle méthode, par exemple avec __invoke(int $id). La contrainte s'écrit alors après la classe, comme après un tableau : ->whereNumber('id').
7. Ranger un contrôleur dans un sous-dossier
Quand un projet compte beaucoup de contrôleurs, vous pouvez ranger ceux des pages d'administration dans un sous-dossier, Admin. Pour créer un contrôleur dans ce sous-dossier, écrivez le nom du dossier devant celui de la classe, séparé par une barre oblique :
php artisan make:controller Admin/StatisticController
INFO Controller [app/Http/Controllers/Admin/StatisticController.php] created successfully.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class StatisticController extends Controller
{
//
}
Artisan a créé le dossier Admin/ et a écrit le namespace App\Http\Controllers\Admin, qui se termine par le nom de ce dossier, comme le demande la règle PSR-4. Il a aussi ajouté la ligne use App\Http\Controllers\Controller;. Sans elle, PHP chercherait la classe mère dans le même namespace que le contrôleur, et la page afficherait Class "App\Http\Controllers\Admin\Controller" not found.
Écrivez une méthode index() qui renvoie une phrase d'exemple :
public function index(): string
{
return 'Statistiques : 1 240 livres, 87 prêts en cours';
}
Dans routes/web.php, l'import contient le nom complet de la classe, avec Admin. Ajoutez-le en tête de la liste des imports, puis ajoutez la route à la fin du fichier :
use App\Http\Controllers\Admin\StatisticController;
// …
Route::get('/admin/statistiques', [StatisticController::class, 'index']);
curl -s http://premier-test-laravel.test/admin/statistiques
php artisan route:list --path=admin
Statistiques : 1 240 livres, 87 prêts en cours
GET|HEAD admin/statistiques .............................. Admin\StatisticController@index
Showing [1] routes
route:list affiche le nom de la classe sans le début commun App\Http\Controllers\, ici Admin\StatisticController. La partie Admin\ vous indique le sous-dossier où ouvrir le fichier.
Le sous-dossier range le code, il ne protège pas la page : n'importe quel visiteur peut ouvrir /admin/statistiques. Vous limiterez l'accès à ce type de page dans le cours « Projet Web Dynamique », qui ajoute les comptes utilisateurs.
8. Quand ça ne marche pas
Trois erreurs reviennent souvent quand vous créez un contrôleur et le reliez à une route. Les deux premières s'affichent dans le navigateur : ouvrez l'adresse de la route concernée, et la page d'erreur de Laravel donne le nom de l'exception, puis une phrase qui en indique la cause. La troisième s'affiche dans le terminal.
L'import oublié. Retirez la ligne use App\Http\Controllers\BookController; de routes/web.php, puis ouvrez /livres. Laravel renvoie une page d'erreur avec le code 500 et ce message :
Target class [BookController] does not exist.
Sans l'import, BookController::class vaut simplement la chaîne "BookController", sans le namespace. Laravel cherche donc une classe qui porte exactement ce nom, et aucune n'existe. route:list échoue lui aussi, pour la même raison :
php artisan route:list --path=livres
ReflectionException
Class "BookController" does not exist
…
Remettez l'import.
La méthode absente. Dans la route books.index, remplacez 'index' par 'list', un nom qui n'existe pas dans BookController, puis ouvrez /livres. Laravel renvoie une erreur 500 :
Call to undefined method App\Http\Controllers\BookController::list()
Cette fois, route:list ne signale rien. Il affiche books.index › BookController@list sans vérifier que la méthode existe. Seule une requête vers la page révèle l'erreur. Remettez 'index'.
Le contrôleur déjà créé. Lancez une seconde fois make:controller avec le même nom :
php artisan make:controller BookController
ERROR Controller already exists.
Artisan n'écrit rien, et votre fichier reste intact. Avec l'option --force, Artisan remplacerait votre fichier par un contrôleur vide, et toutes vos méthodes seraient perdues.
Si vous lancez php artisan make:controller sans nom, Artisan vous pose des questions, en commençant par What should the controller be named?, c'est-à-dire « Quel nom donner au contrôleur ? ». Dans ce cours, écrivez toujours le nom dans la commande : vous voyez ainsi dans votre historique exactement ce que vous avez créé.
9. Et Route::resource() ?
Au chapitre Routing, vous avez lu qu'une seule ligne, Route::resource(), peut déclarer toutes les routes d'une ressource, c'est-à-dire d'un type de données que le site affiche, crée, modifie et supprime, comme des recettes. Chacune de ces routes appelle une méthode du même contrôleur, et Laravel impose le nom de ces sept méthodes. Pour créer un contrôleur qui les contient déjà, ajoutez l'option --resource :
php artisan make:controller RecipeController --resource
INFO Controller [app/Http/Controllers/RecipeController.php] created successfully.
Le fichier contient sept méthodes vides : index, create, store, show, edit, update et destroy. Les méthodes qui travaillent sur une recette précise reçoivent son identifiant dans string $id. Celles qui traitent un formulaire envoyé reçoivent la requête dans Request $request, pour en lire les champs. Dans routes/web.php, ajoutez l'import de RecipeController juste sous celui de PostController. Remplacez ensuite les deux routes du chapitre Routing, /recettes/{id} et /recettes/nouvelle, par cette ligne :
Route::resource('recettes', RecipeController::class);
php artisan route:list --path=recettes
GET|HEAD recettes ............................... recettes.index › RecipeController@index
POST recettes ............................... recettes.store › RecipeController@store
GET|HEAD recettes/create ...................... recettes.create › RecipeController@create
GET|HEAD recettes/{recette} ....................... recettes.show › RecipeController@show
PUT|PATCH recettes/{recette} ................... recettes.update › RecipeController@update
DELETE recettes/{recette} ................. recettes.destroy › RecipeController@destroy
GET|HEAD recettes/{recette}/edit .................. recettes.edit › RecipeController@edit
Showing [7] routes
Les sept noms suivent la convention ressource.action du chapitre Routing, de recettes.index à recettes.destroy. Laravel déclare recettes/create avant recettes/{recette}. Ainsi, l'adresse /recettes/create correspond à la route du formulaire, et le mot create n'est jamais pris pour la valeur de {recette}. C'est le piège que vous avez rencontré au chapitre Routing avec /recettes/nouvelle.
Laravel fabrique le paramètre {recette} et les sept noms de routes à partir du segment d'adresse recettes : c'est pourquoi ils restent en français, alors que le reste de votre code est en anglais. La méthode show() reçoit ce paramètre dans $id : Laravel donne les valeurs dans l'ordre du chemin, comme à la section 3. Les sept méthodes sont encore vides. L'adresse /recettes/nouvelle du chapitre Routing appelle donc maintenant show(), et la page reste blanche. Vous remplirez ces méthodes au chapitre 16, avec un modèle et des formulaires.
À retenir
php artisan make:controller NameControllercrée la classe. Une route la désigne avec[NameController::class, 'method'], à condition d'importer la classe en haut deroutes/web.php.- Une méthode reçoit les paramètres de route dans l'ordre du chemin, et
Request $requestlui donne la requête. - Une méthode peut renvoyer une chaîne, une vue, une redirection ou un tableau, que Laravel convertit en JSON. Grâce au type de retour, PHP signale une erreur si vous oubliez le
return. - Un contrôleur à action unique porte une méthode
__invoke, et sa route donne la classe seule, sans tableau. Target class … does not existsignale un import manquant.Call to undefined methodsignale une route qui nomme une méthode absente du contrôleur, une erreur queroute:listne détecte pas.
Exercices
Comptez 70 minutes pour les exercices, en séance ou à la maison.
Faites les exercices 1 à 3 dans premier-test-laravel. Ajoutez chaque route à la fin de routes/web.php et chaque import en haut du fichier.
1. Le cinéma (20 minutes)
Créez le contrôleur du cinéma avec Artisan :
php artisan make:controller MovieController
Écrivez trois méthodes qui renvoient une chaîne et reliez-les à trois routes nommées. La route de la fiche et celle des séances n'acceptent que des chiffres pour le numéro du film. Inspirez-vous des sections 2 et 3.
| Méthode | Route | Nom | Réponse pour le film 4 |
|---|---|---|---|
index() |
GET /films |
movies.index |
Trois films à l'affiche cette semaine. |
show() |
GET /films/{id} |
movies.show |
Fiche du film 4 |
showtimes() |
GET /films/{id}/seances |
movies.showtimes |
Séances du film 4 : 14 h, 17 h et 20 h 30 |
La phrase de index() contient une apostrophe. Écrivez-la entre guillemets doubles, "Trois films à l'affiche cette semaine.". Entre guillemets simples, PHP croit que la chaîne s'arrête après l et lance une ParseError, une erreur de syntaxe qui fait échouer les trois routes de MovieController.
Vérifiez les quatre adresses et la liste des routes :
curl -s http://premier-test-laravel.test/films
curl -s http://premier-test-laravel.test/films/4
curl -s http://premier-test-laravel.test/films/4/seances
curl -s -i http://premier-test-laravel.test/films/abc
php artisan route:list --path=films
Avant de relancer php artisan make:controller MovieController, écrivez ce que vous attendez, puis lancez la commande et comparez. Que ferait la même commande avec l'option --force ? Ne la lancez pas.
2. Le parking (15 minutes)
Imaginez un parking qui coûte 1,50 EUR de l'heure, sans jamais dépasser 12 EUR pour la journée. Créez un contrôleur à action unique ParkingRateController et reliez-le à la route GET /parking/{hours}, qui n'accepte que des chiffres. Inspirez-vous de la section 6.
La méthode __invoke reçoit le nombre d'heures et renvoie une phrase. Artisan écrit __invoke(Request $request) : remplacez ce paramètre par int $hours, comme show(int $id) à la section 3. Pour le calcul, utilisez deux fonctions de PHP. min($hours * 1.5, 12) renvoie la plus petite des deux valeurs, donc le prix plafonné. number_format($price, 2, ',', ' ') écrit un nombre avec deux décimales et une virgule, comme 4,50.
curl -s http://premier-test-laravel.test/parking/3
curl -s http://premier-test-laravel.test/parking/10
3 heure(s) de parking : 4,50 EUR
10 heure(s) de parking : 12,00 EUR
Modifiez maintenant la route pour qu'elle désigne [ParkingRateController::class, 'index']. Quel message obtenez-vous sur /parking/3, et pourquoi ? Rétablissez la route sans tableau avant de passer à la suite.
3. La météo (25 minutes)
Créez un contrôleur WeatherController. Il garde des relevés inventés dans une propriété :
private array $readings = ['wavre' => 15, 'namur' => 14, 'liege' => 13];
Écrivez trois méthodes et leurs routes. Inspirez-vous des sections 4 et 5.
| Méthode | Route | Réponse |
|---|---|---|
index(Request $request): string |
GET /meteo, nommée weather.index |
Météo demandée pour namur avec ?ville=namur, Météo demandée pour wavre sans paramètre |
reading(string $city): array |
GET /meteo/{city}/releve, pour wavre, namur et liege seulement |
{"city":"liege","temperature":13,"unit":"C"} pour liege |
today(): RedirectResponse |
GET /meteo/aujourdhui |
une redirection 302, avec Location: http://premier-test-laravel.test/meteo?ville=namur |
Comme à la section 5, ajoutez l'import use Illuminate\Http\RedirectResponse; en haut de WeatherController. Sans lui, PHP cherche une classe App\Http\Controllers\RedirectResponse, et /meteo/aujourdhui répond par une erreur 500. La page affiche une TypeError avec ce message :
App\Http\Controllers\WeatherController::today(): Return value must be of type App\Http\Controllers\RedirectResponse, Illuminate\Http\RedirectResponse returned
Pour lire la propriété dans une méthode, écrivez $this->readings[$city]. Vérifiez vos trois routes :
curl -s "http://premier-test-laravel.test/meteo?ville=namur"
curl -s http://premier-test-laravel.test/meteo
curl -s -i http://premier-test-laravel.test/meteo/liege/releve
curl -s -I http://premier-test-laravel.test/meteo/aujourdhui
Retirez enfin la contrainte de la route du relevé et demandez /meteo/paris/releve. Quel code obtenez-vous, et quelle ligne de reading() en est la cause ? Remettez ensuite la contrainte.
4. Lire les routes et les erreurs (sur papier, 10 minutes)
À la fin des exercices, php artisan route:list --except-vendor affiche entre autres ces six lignes. Chez vous, le numéro de ligne de ping peut être différent.
GET|HEAD admin/statistiques ............................. Admin\StatisticController@index
GET|HEAD films/{id} .................................. movies.show › MovieController@show
GET|HEAD livres/{id}/donnees ........................................ BookController@data
GET|HEAD meteo .................................. weather.index › WeatherController@index
GET|HEAD parking/{hours} .......................................... ParkingRateController
GET|HEAD ping ......................................................... routes/web.php:19
- Pour chaque ligne, écrivez le fichier à ouvrir et la méthode, ou la fonction, que Laravel appelle.
- Pour chacun de ces trois messages, écrivez la cause et la correction.
Target class [MovieController] does not exist.Call to undefined method App\Http\Controllers\WeatherController::readings()App\Http\Controllers\MovieController::index(): Return value must be of type string, none returned