Le contrôleur des Users
Pour ça, rendez-vous dans le fichier UsersController (app -> Http -> Controllers -> UsersController.php). On va y retrouver le contenu suivant :
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\n\nclass UsersController extends Controller\n{\n //\n}\n","filename":"UsersController.php"}
Il s'agit du code le plus minimal qui soit pour un controller chez Laravel. Y'a-t-il des choses que tu ne comprends pas ?
Hum... À quoi sert la classe Controller parente ? Et la classe Request qu'on "use" ?
La classe Controller permet d'utiliser dans nos controllers les fonctionnalités offertes par Laravel pour gérer les droits d'accès, les vues...
La classe Request permet comme son nom l'indique de gérer les requêtes ! On l'utilisera quand on doit récupérer des informations envoyées par formulaire.
Voyons l'utilisation du contrôleur avec une première utilisation : afficher les utilisateurs. Je rappelle que notre route est la suivante (avant qu'on fasse nos groupes, mais le résultat est bien celui-ci dans tous les cas) :
{"language":"application/x-httpd-php","content":"<?php\n\nRoute::get('/users', [UsersController::class, 'index'])->middleware(['auth', 'admin'])->name('users');\n","filename":"routes.php"}
Notre méthode doit donc s'appeler "index". On ne lui passe aucun arguments car la route n'a pas d'attributs et qu'on ne va pas traiter de formulaire.
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\n\nclass UsersController extends Controller\n{\n public function index() \n {\n \t// On récupère tous les utilisateurs\n \t// On les affiche\n }\n}\n","filename":"UsersController.php"}
Comme tu le vois la première des étapes va être de récupérer tous les utilisateurs en bdd. Je te laisse faire.
Quoi ? Bon ok... Alors si j'applique mes cours, il faut que je respecte le modèle MVC. Donc je vais devoir créer un manager qui me donne une méthode qui me retourne un SELECT * FROM users; en gros. Fiou, mais comment faire ça avec Laravel....
Ahah, je t'ai laissé chercher volontairement pour que tu comprennes la puissance d'Eloquent, l'ORM de Laravel (j'en ai parlé dans la partie liée aux Modèles, souviens-toi ! ). Pour récupérer tous les utilisateurs, voici la solution :
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse App\\Models\\User;\n\nclass UsersController extends Controller\n{\n public function index() \n {\n \t$users = User::all();\n \tdd($users);\n }\n}\n","filename":"UsersController.php"}
C'est tout ? User::all() ?
Hé oui, c'est tout. Avec ça, tu récupèreras tous les utilisateurs en base de données ;). C'est là l'intérêt d'utiliser un framework et notamment Laravel : tout est simplifié.
Ok super. Et c'est quoi dd() ?
Un helpeur de Laravel. C'est un var_dump amélioré. Tiens, essayons justement. Connecte toi sur le site en tant qu'administrateur (je rappelle qu'on a mis le middleware Admin sur la route, si tu te connectes en tant que user simple tu ne pourras pas accéder à la page) et va ensuite sur la page tonsite/users. Tu vas voir ça :
Tu vois que ce qui est retourné est une Collection. Les collections sont un format de donnée offert par Laravel qui est une sorte de tableau amélioré où on peut faire plein d'opérations de traitement rapidement dessus.
Dans cette collection on a la liste de tous les utilisateurs, sous forme de classe "App\Models\User".
Du coup si on veut afficher tous les utilisateurs, il nous suffit d'itérer sur la collection :
{"language":"application/x-httpd-php","content":"<?php\n\tpublic function index() \n {\n \t$users = User::all();\n \tforeach($users as $user) {\n \t\techo $user->id;\n \t\techo $user->name;\n \t}\n }","filename":"UsersController.php"}
Ok super, mais comment je fais pour afficher ces données dans une vue ?
On retourne "view", comme on a vu dans les routes !
{"language":"application/x-httpd-php","content":"<?php\n\tpublic function index() \n {\n \t$users = User::all();\n \treturn view('users.index', compact('users'));\n }","filename":"UsersController.php"}
users.index ? C'est quoi ?
C'est le chemin vers la vue. Ici on dit qu'on va chercher dans le dossier des vues le dossier "users" et dans ce dossier le fichier "index.blade.php". Pourquoi index.blade.php et pas index.php me diras-tu ? Hé bien, Laravel intègre son propre moteur de template nommé "blade". Ce moteur de template gère le lien entre les contrôleurs et les vues. De manière à bien délimiter les vues qui utilisent Blade et les vues qui ne l'utilisent pas, on va préciser dans l'extension ".blade" ou non ! On reviendra sur les vues dans une partie du cours qui arrive très vite ;).
Bref, Il faut donc créer ce dossier "users" dans les vues et ce fichier "index.blade.php". Je te laisse faire.
Je crée ça.
Bon, je crée les prototypes des autres méthodes de la classe UsersController. Regarde ce que ça donne. Tu as des questions ?
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse App\\Models\\User;\n\nclass UsersController extends Controller\n{\n public function index() \n {\n \t$users = User::all();\n \treturn view('users.index', compact('users'));\n }\n\n public function show(User $user) {\n\n }\n\n public function edit(User $user) {\n\n }\n\n public function update(Request $request, User $user) {\n\n }\n\n public function destroy(User $user) {\n\n }\n}\n","filename":"UsersController.php"}
Certaines méthodes prennent un paramètre comme Request ou User... Mais dans notre routeur je n'ai pas souvenir d'avoir passé ces paramètres à nos contrôleurs !
Tu as tout à fait raison. En réalité, c'est Laravel qui va passer ces paramètres pour nous. C'est ce qu'on appelle l'injection de dépendances. Request sera accessible tout le temps mais on s'en sert en général pour traiter les formulaires. Ensuite, User est passé grâce à l'implicite binding qu'on a vu quand on faisait nos routes ! C'est grâce au paramètre dans nos routes comme dans celle-ci : Route::get('/users/profile/{user}', ...) !
Je vois ! C'est super pratique.
Du coup je te laisse faire les méthodes show, edit et essayer d'autres si tu veux. Le but est simplement de retourner la vue.
Voici la correction :
{"language":"application/x-httpd-php","content":"<?php\nclass UsersController extends Controller\n{\n public function index() \n {\n \t$users = User::all();\n \treturn view('users.index', compact('users'));\n }\n\n public function show(User $user) {\n \treturn view('users.show', compact('user'));\n }\n\n public function edit(User $user) {\n \treturn view('users.edit', compact('user'));\n }\n\n public function update(Request $request, User $user) {\n\n }\n\n public function destroy(User $user) {\n \t$user->delete();\n \treturn redirect()->back()->with('success', 'L\\'utilisateur a bien été supprimé');\n }\n}","filename":"UsersController.php"}
C'est quoi redirect ?
Un helpeur qui nous permet de rediriger vers une autre page. Ici on veut rediriger à la page d'avant et en plus on met un message flash nommé "success". C'est un message stocké en session qui s'effacera une fois qu'il aura été affiché une fois.
Comment je pouvais savoir pour utiliser delete() ?
Ce sont les méthodes offertes par Eloquent. Tu en as la liste ici : [les méthodes avec eloquent]. Le mieux est que tu regardes cette page et que tu observes tout ce qu'il est possible de faire.
Bon, il ne reste plus que le update(). Pour ça, on doit avant d'enregistrer en base de données vérifier les données du formulaire. Car je le rappelle, "Never Trust User Inputs".
Heureusement, Laravel met à notre disposition dans la classe Request une méthode validate qui permet de valider les inputs selon des critères que l'on définit. Les critères peuvent être : je veux que ce champ ait une valeur comprise entre 2 et 10 caractères, que ce soit un nombre, qu'il ne soit pas vide, que ce soit une adresse email...
Sans la méthode validate de la classe Request, on devrait vérifier les inputs à la main avec une suite de if() et des fonctions comme mb_strlen() pour compter les caractères, empty(), filter_var() pour vérifier des formats etc etc.
Ce qu'il y a de bien, c'est que Laravel a déjà un tas de règles prédéfinies pour la méthode validate() que l'on retrouve ici [règles disponibles].
Dans le cas du update, on va autoriser une personne à modifier son profil. Il peut modifier son "name" et son "email". On ne traitera pas le mot de passe dans ce cours. As-tu des critères de validation à me proposer pour ces données ?
Voilà ce que je te propose :
- name : entre 2 et 50 caractères, requis
- email : requis, format d'email
C'est parfait. Du coup, voici un exemple pour valider ces données :
{"language":"application/x-httpd-php","content":"<?php\npublic function update(Request $request, User $user) {\n // Afficher une valeur : \n dd($request->input('name')); // On considère que notre requête contient un champ \"name\"\n // On valide les données\n $validatedData = $request->validate([\n 'name' => ['required', 'min:2', 'max:50'],\n 'email' => 'required|email',\n ]);\n}","filename":"UsersController.php"}
Je te montre ici plusieurs choses. D'abord, comment récupérer la valeur d'une donnée passée dans le formulaire : il nous suffit d'utiliser $request->input(). Ensuite, je te montre comment valider les données avec des règles de Laravel et te montre que tu as deux façons d'utiliser les règles : soit avec un tableau, soit en séparant les règles par un "pipe" (|). Par exemple, à la ligne 8 nous avons : 'email' => 'required|email'. Cela signifie que email est obligatoire ( required ) et qu'il doit ressembler à un mail ( des caractères, puis le symbole "@", puis des caractères etc..
C'est une syntaxe très claire ! Mais du coup, comment je fais pour afficher un message d'erreur si quelque chose ne va pas ?
En fait, dès qu'une règle ne sera pas respectée, Laravel génèrera tout seul pour nous une erreur et ne continuera pas l'exécution du code. Il y aura une redirection vers la page précédente avec une donnée de session qui contiendra toutes les erreurs. On pourra alors les afficher. Du coup, dans notre méthode update() on peut continuer d'écrire notre code en toute confiance car il ne sera exécuté que si toutes les validations d'input sont passées ! Les données validées sont ensuite stockées dans le tableau $validatedData qui sera de la forme :
{"language":"application/x-httpd-php","content":"<?php\n$validatedData = ['name'=>$request->input('name'), 'email'=>$request->input('email'), 'submit'=>$request->input('submit'), 'csrf'=>$request->input('csrf')];","filename":""}
Mais c'est quoi ces champs "submit" et "CSRF" ?
Je te rappelle que $request contient toutes les données envoyées par notre formulaire. En général, on a un input type submit qui a le nom "submit", c'est donc normal qu'on le retrouve dans la $request ! Vu qu'on a mis aucune règle de validation dessus (aucun intérêt de le faire), il est considéré comme une donnée validée ;). Concernant CSRF, c'est un input caché généré par Laravel pour se protéger de la faille du même nom. Les failles CSRF (explications sur la faille CSRF, ou encore CSRF par l'OWASP) sont très courantes dans les sites web, Laravel nous offre un moyen simple de nous en prémunir. Nous n'avons rien à faire, c'est lui qui fait tout ! Il est dans $validatedData pour la même raison que "submit".
J'ai compris ! Alors maintenant, on doit enregistrer ces changements dans notre base de données. J'imagine que Eloquent met à notre disposition une méthode super cool pour mettre à jour l'utilisateur ?
En effet, elle s'appelle update. Et tu sais quoi ? On a juste à lui passer validatedData. Il va tout faire pour nous ensuite : modifier les données name et email si une modification a eu lieu, et laisser les 2 autres champs de côté. Et pour quelle raison il va laisser les 2 autres champs de côté ?
Ce ne serait pas grâce au $fillable qu'on a rempli dans nos modèles ?
Bingo, je vois que tu commences à comprendre. C'est bien !
Notre contrôleur a donc la tête suivante :
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse App\\Models\\User;\n\nclass UsersController extends Controller\n{\n public function index() \n {\n \t$users = User::all();\n \treturn view('users.index', compact('users'));\n }\n\n public function show(User $user) {\n \treturn view('users.show', compact('user'));\n }\n\n public function edit(User $user) {\n \treturn view('users.edit', compact('user'));\n }\n\n public function update(Request $request, User $user) {\n $validatedData = $request->validate([\n 'name' => ['required', 'min:2', 'max:50'],\n 'email' => 'required|email',\n ]);\n\n $user->update($validatedData);\n\n return redirect()->back()->with('success', 'Les informations ont bien été modifiées');\n }\n\n public function destroy(User $user) {\n \t$user->delete();\n \treturn redirect()->back()->with('success', 'L\\'utilisateur a bien été supprimé');\n }\n}\n","filename":"UsersController.php"}
Notre contrôleur des posts
Une fois qu'on a compris comment faire un contrôleur, les autres ça va tout seul :p. On a cependant 2-3 choses à voir qui vont différer. Je t'avais dit qu'on pouvait mettre des middlewares dans nos contrôleurs également. Ça va être utile ici sur certaines méthodes car seul l'administrateur peut créer, modifier ou supprimer des articles.
Pour cela, on va utiliser une méthode disponible par l'héritage de la classe Controller : middleware. On l'utilise en précisant le middleware à appliquer et sur quelles méthodes, comme suit :
{"language":"application/x-httpd-php","content":"<?php\npublic function __construct() {\n $this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']);\n}","filename":"PostsController.php"}
Je m'attendais à plus dur, en fait c'est super simple.
Tu l'as dit, c'est super simple !
Bon, et bien maintenant c'est toujours pareil, le code est semblable à celui du UsersController :
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\n\nuse App\\Models\\Post;\n\nclass PostsController extends Controller\n{\n\n public function __construct() {\n $this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']);\n }\n /**\n * Display a listing of the resource.\n *\n * @return \\Illuminate\\Http\\Response\n */\n public function index()\n {\n return view('posts.index', ['posts'=>Post::all()]);\n }\n\n /**\n * Show the form for creating a new resource.\n *\n * @return \\Illuminate\\Http\\Response\n */\n public function create()\n {\n return view('posts.create');\n }\n\n /**\n * Store a newly created resource in storage.\n *\n * @param \\Illuminate\\Http\\Request $request\n * @return \\Illuminate\\Http\\Response\n */\n public function store(Request $request)\n {\n $validatedData = $request->validate([\n 'title'=>'required|min:2|max:255',\n 'content'=>'required|min:10'\n ]);\n $post = Post::create($validatedData);\n\n return redirect()->back()->with('success', 'Le post a été créé');\n }\n\n /**\n * Display the specified resource.\n *\n * @param \\App\\Models\\Post $post\n * @return \\Illuminate\\Http\\Response\n */\n public function show(Post $post)\n {\n return view('posts.show', compact('post'));\n }\n\n /**\n * Show the form for editing the specified resource.\n *\n * @param \\App\\Models\\Post $post\n * @return \\Illuminate\\Http\\Response\n */\n public function edit(Post $post)\n {\n return view('posts.edit', compact('post'));\n }\n\n /**\n * Update the specified resource in storage.\n *\n * @param \\Illuminate\\Http\\Request $request\n * @param \\App\\Models\\Post $post\n * @return \\Illuminate\\Http\\Response\n */\n public function update(Request $request, Post $post)\n {\n $validatedData = $request->validate([\n 'title'=>'required|min:2|max:255',\n 'content'=>'required|min:10'\n ]);\n $post->update($validatedData);\n\n return redirect()->back()->with('success', 'Le post a été modifié');\n }\n\n /**\n * Remove the specified resource from storage.\n *\n * @param \\App\\Models\\Post $post\n * @return \\Illuminate\\Http\\Response\n */\n public function destroy(Post $post)\n {\n $post->delete();\n return redirect()->back()->with('success', 'Le post a été supprimé');\n }\n}","filename":"PostsController.php"}
Mais attends... Je réfléchis bien et là je vois que tu crées des posts, très bien. Mais les posts sont créés par quelqu'un, d'ailleurs on avait mis belongsTo dans notre entité Post ! Là, je crois qu'ils ne sont liés avec personne quand on crée un... Comment ça se passe ?
Ça, c'est une excellente remarque. Eloquent ne fait pas ça de manière automatique, il va falloir qu'on lui dise. En fait, on peut transformer notre "Créer un Post à partir des données suivantes" en "Créer un post pour l'utilisateur truc à partir des données suivantes". Pour ça, ce n'est pas l'entité Post qu'on va appeler mais l'entité User. On va récupérer l'utilisateur courant et lui associer un Post qui va être créé dans le même temps.
Ok mais comment je fais pour accéder à l'utilisateur qui est connecté ?
On peut utiliser le helpeur request() ou bien la classe Auth. On va utiliser le helpeur request(). Pour accéder aux données de l'utilisateur courant, on utilisera request()->user(). Cette ligne de code nous retourne une instance de la classe User préremplie avec les données de l'utilisateur connecté ! Tu peux t'en assurer en faisant un dd(request()->user()).
Bon, maintenant on va tirer profit des relationships qu'on a définies dans notre entités. On a défini la méthode "posts" dans User.php. Celle-ci va permettre à Eloquent de faire tout ce qu'on veut avec les posts de l'utilisateur. Notamment d'en créer !
On peut donc modifier notre code :
{"language":"application/x-httpd-php","content":"<?php\n$post = Post::create($validatedData);\n// Qu'on transforme en : \n$post = request()->user()->posts()->create($validatedData);","filename":""}
Et voilà !
Le contrôleur des commentaires
Le code est sensiblement le même que pour les contrôleurs précédents hormis quelques exceptions.
Ici, on va avoir le même problème pour ajouter un commentaire lié à un post. Heureusement pour nous, "post_id" sera passé en champ hidden du formulaire de création de commentaires. Ensuite, il nous suffit de récupérer le post en base de données grâce à la méthode find() de Eloquent qui récupère l'entité en base de données qui a l'ID passé en paramètre de find. Après, on fait comme on a vu au dessus !
On aura aussi un autre changement : on doit rajouter notre méthode pour signaler un commentaire. Le but ici est de passer "reported" à 1. Rien de plus simple, on peut utiliser la méthode save() offerte par notre ORM.
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\{Post, Comment};\nuse Illuminate\\Http\\Request;\n\nclass CommentsController extends Controller\n{\n\n public function __construct() {\n $this->middleware('admin')->only(['index', 'destroy', 'edit', 'update']);\n }\n /**\n * Display a listing of the resource.\n *\n * @return \\Illuminate\\Http\\Response\n */\n public function index()\n {\n return view('comments.index', ['comments'=>Comment::all()]);\n }\n\n /**\n * Show the form for creating a new resource.\n *\n * @return \\Illuminate\\Http\\Response\n */\n public function create()\n {\n return view('comments.create');\n }\n\n /**\n * Store a newly created resource in storage.\n *\n * @param \\Illuminate\\Http\\Request $request\n * @return \\Illuminate\\Http\\Response\n */\n public function store(Request $request)\n {\n $validatedData = $request->validate([\n 'post_id'=>'required|numeric|exists:App\\Models\\Post,id',\n 'title'=>'required|min:2|max:255',\n 'author'=>'required|min:2|max:50',\n 'content'=>'required|min:5',\n ]);\n $comment = Post::find($request->input('post_id'))->comments()->create($validatedData);\n\n return redirect()->back()->with('success', 'Le commentaire a été créé');\n }\n\n /**\n * Display the specified resource.\n *\n * @param \\App\\Models\\Comment $comment\n * @return \\Illuminate\\Http\\Response\n */\n public function show(Comment $comment)\n {\n return view('comments.show', compact('comment'));\n }\n\n /**\n * Show the form for editing the specified resource.\n *\n * @param \\App\\Models\\Comment $comment\n * @return \\Illuminate\\Http\\Response\n */\n public function edit(Comment $comment)\n {\n return view('comments.edit', compact('comment'));\n }\n\n /**\n * Update the specified resource in storage.\n *\n * @param \\Illuminate\\Http\\Request $request\n * @param \\App\\Models\\Comment $comment\n * @return \\Illuminate\\Http\\Response\n */\n public function update(Request $request, Comment $comment)\n {\n $validatedData = $request->validate([\n 'title'=>'required|min:2|max:255',\n 'author'=>'required|min:2|max:50',\n 'content'=>'required|min:5',\n ]);\n $comment->update($validatedData);\n\n return redirect()->back()->with('success', 'Le commentaire a été modifié');\n }\n\n /**\n * Remove the specified resource from storage.\n *\n * @param \\App\\Models\\Comment $comment\n * @return \\Illuminate\\Http\\Response\n */\n public function destroy(Comment $comment)\n {\n $comment->delete();\n return redirect()->back()->with('success', 'Le commentaire a été supprimé');\n }\n\n public function report(Comment $comment) {\n $comment->reported = 1;\n $comment->save();\n // On aurait pu aussi utiliser la méthode update \n // $comment->update(['reported'=>1]);\n\n return redirect()->back()->with('success', 'Le commentaire a été signalé');\n }\n}","filename":"CommentsController.php"}
Je comprends le truc, par contre c'est quoi ta règle "exists" dans la validation ?
Elle permet de vérifier que pour l'entité passée en paramètre ("App\Models\Post"), la valeur du formulaire ("post_id") est présente dans la colonne spécifiée ("id").
J'ai tout compris, je suis très content d'être arrivé jusque là. Il ne nous reste plus qu'à tester et afficher tout ça dans des vues, c'est cela ?
Tout à fait !
Ce qui a été vu dans cette partie :
Si tu veux aller plus loin, tu peux te renseigner sur le lazy et l'eager loading pour récupérer notamment les commentaires associés à chaque post de manière plus optimisée.
J'ai terminé cette partie - La création de contrôleurs
- L'utilisation des middlewares au sein des constructeurs
- La validation des inputs avec $request
- La récupération de l'utilisateur connecté avec request()->user()
- Des méthodes d'Eloquent pour manipuler nos entités
Si tu veux aller plus loin, tu peux te renseigner sur le lazy et l'eager loading pour récupérer notamment les commentaires associés à chaque post de manière plus optimisée.