Pour commencer, je te propose de designer le layout. Il y en a déjà un de proposé (guest.blade.php) qui utilise les view composers, mais nous allons utiliser ici l'héritage (ce qui est possible depuis Laravel 8 vs ce qui était tout le temps fait dans les versions précédentes) que tu pourras rencontrer dans plusieurs autres projets Laravel je n'en doute pas.
Ok ça me va !
Le layout et la page d'accueil
Commençons par créer notre fichier de layout, c'est à dire par créer le design qui va être commun à toutes les pages de la partie accessible à tous. On va l'appeler "front.blade.php". Je te préviens d'avance, ce ne sera pas très "beau" ni très "responsive". Je fais au plus simple, n'hésite pas à modifier par la suite !
Je comprends, le principal c'est la partie Laravel.
Oui, merci pour ta compréhension :p. Bon, alors pour faire notre layout il nous faut savoir quelles parties seront différentes entre chaque page. En général, on aura :
- Le titre de la page
- Sa meta description
- Peut être du style particulier
- Le contenu principal
- Peut être des scripts particuliers
On doit donc faire en sorte que dans chaque vue utilisant notre layout on puisse renseigner ces différents éléments. Pour ça, on va utiliser dans le layout la fonction @yield de Blade. Cette fonction définit un "nom de variable" et un contenu par défaut facultatif. Ce nom de variable peut alors être réutilisé dans les vues pour renseigner un contenu qui lui est propre !
Je te propose donc le code suivant pour la layout :
{"language":"text/html","content":"<!DOCTYPE html>\n<html lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n\n <title>@yield('title', 'Blog')</title>\n\n <meta name=\"description\" content=\"@yield('meta', 'Articles lunaires et heureux sur ce blog merveilleux')\">\n\n <!-- Fonts -->\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap\">\n\n <!-- Styles -->\n <link rel=\"stylesheet\" href=\"{{ asset('css/app.css') }}\">\n @yield('scripts')\n\n <!-- Scripts -->\n <script src=\"{{ asset('js/app.js') }}\" defer></script>\n @yield('style')\n </head>\n <body class=\"font-sans antialiased\">\n <div class=\"min-h-screen bg-gray-100\">\n <header class=\"fixed top-0 left-0 w-full space-x-1 sm:h-12 bg-white flex items-center sm:space-x-5 sm:pl-8 flex-wrap\">\n <span class=\"text-blue-800 inline-block mr-8\">Mon Super Blog</span>\n <a href=\"/\">Accueil</a>\n <a href=\"{{ route('posts.index') }}\">Les posts</a>\n @auth\n <a href=\"{{ route('users.edit', Auth::user()) }}\">Profil</a>\n <form action=\"{{ route('logout') }}\" method=\"POST\">\n @csrf\n <button type=\"submit\" href=\"{{ route('logout') }}\">Déconnexion</button>\n </form>\n @if(Auth::user()->role === \\App\\Enums\\UserRole::Admin)\n <a href=\"{{ route('dashboard') }}\">Dashboard</a>\n @endif\n @else\n <a href=\"{{ route('login') }}\">Connexion</a>\n @endauth\n </header>\n\n\n <!-- Page Content -->\n <main class=\"mt-14 mx-2 sm:mx-8\">\n @yield('content')\n </main>\n </div>\n </body>\n</html>","filename":"resources/views/layouts/front.blade.php"}
Tu comprends ce que j'ai fait ?
Oui j'ai compris, le yield fonctionne un peu comme une variable dont le contenu proviendrait d'un ob_get_clean() en PHP classique.
C'est tout à fait ça ! @auth est une condition qui vérifie quant à elle si on est authentifié. Ensuite, j'utilise Auth, je t'en avais un peu parlé. On peut utiliser cette classe pour accéder aux données de l'utilisateur en faisant Auth::user().
Ok ! Par contre pourquoi tu utilises un formulaire et pas un lien pour la déconnexion ?
Il suffit de regarder la méthode supportée par la route qui gère le logout. Si tu fais php artisan route:list, tu remarqueras que Laravel Breeze qui gère l'authentification ne supporte que le POST pour le logout. On doit donc utiliser un formulaire et le token CSRF ;)
On va maintenant modifier l'accueil de notre site du coup. Il s'agit pour cela de modifier la vue "welcome.blade.php" .
Pour utiliser notre layout, il va falloir indiquer qu'on va en hériter. Et pour ça, c'est comme en PHP classique, on va utiliser extends. Enfin, une fonction de Blade qui s'appelle extends :p. Et on va aussi utiliser la fonction @section pour renseigner les variables qu'on a définies avec @yield dans le layout parent.
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'Accueil du blog')\n@section('content')\n <h1 class=\"text-3xl text-blue-700 my-8\">Bienvenue sur mon super blog</h1>\n <p>Retrouvez des articles de qualité sur des sujets variés qui embelliront vos journées !</p>\n <h2 class=\"text-2xl my-6\">Le dernier article</h2>\n\n@endsection","filename":"resources/views/welcome.blade.php"}
C'est clair pour moi. Par contre comment on va faire pour afficher le dernier post ?
Excellente question. On rappelle que cette tâche, de récupérer le dernier post et de le passer à la vue, est dédiée plutôt à la partie Controller du modèle MVC. Mais cette vue est appelée directement dans les routes. Cela a un certain avantage : c'est rapide et Laravel va mettre moins de temps à afficher la page. C'est donc une technique conseillée quand on a besoin que d'afficher la vue et de ne faire aucun traitement (page statique, mentions légales...). Mais nous là on va devoir créer un nouveau controller que je vais appeler "FrontController". Bilan :
{"language":"application/x-httpd-php","content":"<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\nuse App\\Http\\Controllers\\{CommentsController, PostsController, UsersController, FrontController};\n\n// ...\n\nRoute::get('/', [FrontController::class, 'welcome']);\n\n// ...","filename":"routes/web.php"}
{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse App\\Models\\Post;\n\nclass FrontController extends Controller\n{\n public function welcome() \n {\n \t$post = Post::latest('id')->first();\n \treturn view('welcome', compact('post'));\n }\n}","filename":"FrontController.php"}
Encore de nouvelles méthodes offertes par Eloquent latest() et first() ?
Tout à fait. Latest va récupérer les éléments dont la valeur de la colonne passée en argument est la plus récente. Ici, on récupère donc les derniers éléments insérés en fonction de l'ID. Et combien de derniers éléments ? Hé bien, un seul, grâce à first(). En bref, on récupère donc le dernier élément enregistré.
Du coup on peut terminer notre vue :
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'Accueil du blog')\n@section('content')\n <h1 class=\"text-3xl text-blue-700 my-8\">Bienvenue sur mon super blog</h1>\n <p>Retrouvez des articles de qualité sur des sujets variés qui embelliront vos journées !</p>\n <h2 class=\"text-2xl my-6\">Le dernier article</h2>\n @if($post)\n <div class=\"bg-white shadow w-full sm:w-1/2\">\n <h3 class=\"text-xl text-blue-500\">{{ $post->title }}</h3>\n <p>{{ str($post->content)->limit(10) }}</p>\n </div>\n @else\n Il n'y a pas encore d'article sur notre site :'(. \n @auth <a class=\"underline text-blue-400\" href=\"{{ route('posts.create') }}\">Créez-en un ! </a> @endauth\n @endif\n@endsection","filename":"resources/views/welcome.blade.php"}
str($post->content)->limit(10) ? Quoi qu'est-ce donc ?
Je t'ai souvent parlé des helpeurs de Laravel. Il en offre aussi pour la manipulation des chaines de caractères ! Et avec Laravel 9, la syntaxe a été simplifiée. Pour plus d'infos : https://laravel.com/docs/9.x/helpers#method-str. Cette fonction permet de remplacer les caractères qui dépassent notre limite par des "points" (...) (mais on peut aussi remplacer par ce qu'on veut). Dans notre cas j'ai fixé une limite à 10 caractères juste pour que tu voies ce que ça donne, mais évidemment on pourrait plutôt mettre 100 ou 150 ;)
Oula en effet il y a plein de helpeurs sur la page que tu m'as donnée ! Plein de fonctions intéressantes, je vais regarder ça plus tard !
Oui, avant de faire une fonction hyper compliquée pour agir sur des chaines de caractères par exemple, n'hésite pas à regarder du côté des helpeurs ;)
Ça marche. Par contre je crois que tu as oublié de faire un lien pour lire l'article. Je fais ça, ça marche ?
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'Accueil du blog')\n@section('content')\n <h1 class=\"text-3xl text-blue-700 my-8\">Bienvenue sur mon super blog</h1>\n <p>Retrouvez des articles de qualité sur des sujets variés qui embelliront vos journées !</p>\n <h2 class=\"text-2xl my-6\">Le dernier article</h2>\n @if($post)\n <div class=\"bg-white shadow w-full sm:w-1/2\">\n <h3 class=\"text-xl text-blue-500\">{{ $post->title }}</h3>\n <p>{{ str($post->content)->limit(10) }}</p>\n <a href=\"{{ route('posts.show', $post) }}\">Lire l'article</a>\n </div>\n @else\n Il n'y a pas encore d'article sur notre site :'(. \n @auth <a class=\"underline text-blue-400\" href=\"{{ route('posts.create') }}\">Créez-en un ! </a> @endauth\n @endif\n@endsection","filename":"resources/views/welcome.blade.php"}
Oui c'est parfait félicitations ! D'ailleurs, on va passer à la vue pour afficher les posts du coup, et en afficher aussi un en particulier.
Afficher tous les posts
Je ne crois pas qu'il y ait de difficultés à ce sujet. Du coup je vais ajoute une directive Blade que tu n'as pas encore vue : @forelse. C'est une boucle @foreach classique dans laquelle on peut préciser un message dans le cas où notre tableau parcouru est vide. Très simple à comprendre :
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'La liste des posts de mon super blog')\n@section('content')\n <h1 class=\"text-3xl text-blue-700 my-8\">Les articles</h1>\n <p>Tous les articles sont ici, profitez-en ;)</p>\n @forelse($posts as $post)\n\t <div class=\"bg-white shadow w-full sm:w-1/2\">\n\t <h3 class=\"text-xl text-blue-500\">{{ $post->title }}</h3>\n\t <p>{{ str($post->content)->limit(10) }}</p>\n\t <a href=\"{{ route('posts.show', $post) }}\">Lire l'article</a>\n\t </div>\n\t@empty\n Oups, il n'y a pas encore d'article :'(\n @endforelse\n@endsection","filename":"resources/views/posts/index.blade.php"}
Afficher un seul post
Là on doit faire plus de choses :
- Afficher le post
- Afficher les commentaires
- Afficher le formulaire pour laisser un commentaire.
Le code est grand mais il n'y a rien que tu connaisses pas déjà !
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'Lire l\\'article '.$post->title)\n@section('content')\n\t<section class=\"mx-auto py-2 my-6\">\n\t <h1 class=\"text-3xl text-blue-700 my-8 underline\">{{ $post->title}}</h1>\n\t <p>{{ $post->content }}</p>\n\t</section>\n\t<h2 class=\"text-xl text-blue-500 underline\">Les commentaires liés à ce post</h2>\n\t@forelse($post->comments as $comment)\n\t\t<section class=\"mx-auto py-2 my-6 border border-blue-100\">\n\t\t <h3 class=\"text-base text-blue-400\">{{ $comment->title }} par {{ $comment->author }}</h3>\n\t\t <p class=\"text-sm\">{{ $comment->content }}</p>\n\t\t @if(Auth::check() and Auth::user()->role === \\App\\Enums\\UserRole::Admin)\n\t\t \t<a class=\"inline-block mt-4 underline text-blue-400 text-xs\" href=\"{{ route('comments.edit', $comment) }}\">Editer le commentaire</a>\n\t\t @endif\n\t\t</section>\n\t@empty\n\t\t<p>Soyez le premier à laisser un commentaire ! </p>\n\t@endforelse\n <form action=\"{{ route('comments.store') }}\" method=\"POST\" class=\"my-6 sm:w-1/2 mx-auto\">\n \t<h2 class=\"text-xl\">Laisser un commentaire</h2>\n \t @if(session('success'))\n <div class=\"text-xl text-green-400\">\n {{ session('success') }}\n </div>\n @endif\n @csrf\n <input type=\"hidden\" name=\"post_id\" value=\"{{ $post->id }}\">\n <div class=\"px-4 py-5 bg-white sm:p-6\">\n \t<div class=\"py-2\">\n <label for=\"author\" class=\"block text-sm font-medium text-gray-700\">Nom</label>\n <input type=\"text\" name=\"author\" id=\"author\" class=\"mt-1 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\" @auth value=\"{{ old('author', Auth::user()->name) }}\" @else value=\"{{ old('author') }}\" @endauth>\n @error('author')\n <span class=\"text-red-600\">{{ $message }}</span>\n @enderror\n </div>\n <div class=\"py-2\">\n <label for=\"title\" class=\"block text-sm font-medium text-gray-700\">Titre du commentaire</label>\n <input type=\"text\" name=\"title\" id=\"title\" class=\"mt-1 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\" value=\"{{ old('title') }}\">\n @error('title')\n <span class=\"text-red-600\">{{ $message }}</span>\n @enderror\n </div>\n <div class=\"py-2\">\n <label for=\"content\" class=\"block text-sm font-medium text-gray-700\">Contenu</label>\n <textarea id=\"content\" name=\"content\" rows=\"3\" class=\"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md\">{{ old('content') }}</textarea>\n @error('content')\n <span class=\"text-red-600\">{{ $message }}</span>\n @enderror\n </div>\n <div class=\"py-2\">\n <input type=\"submit\" class=\"cursor-pointer inline-flex items-center w-1/4 py-4 border border-gray-400 shadow-sm text-base font-medium rounded-md text-gray-700 bg-white justify-center\" value=\"Créer\">\n </div>\n </div>\n </form>\n@endsection","filename":"resources/views/posts/show.blade.php"}
Long code mais je crois avoir tout compris. Si j'ai des questions je reviens vers toi !
Oui et toi aussi lecteur, n'hésite pas si tu as des questions à les poser en commentaire ou en demandant une assistance !
L'édition du profil
Le code est assez simple encore une fois, je ne pense pas qu'il posera de difficultés.
{"language":"text/html","content":"@extends('layouts.front')\n@section('title', 'Modification de mon profil')\n@section('content')\n<div class=\"bg-white overflow-hidden shadow-sm sm:rounded-lg\">\n <h3 class=\"text-3xl mx-4 my-4\">Modification de mon profil</h3>\n @if(session('success'))\n <div class=\"text-xl text-green-400\">\n {{ session('success') }}\n </div>\n @endif\n <form action=\"{{ route('users.update', $user) }}\" method=\"POST\" class=\"mx-auto\">\n @csrf\n @method('PUT')\n <div class=\"px-4 py-5 bg-white sm:p-6\">\n <div class=\"py-2\">\n <label for=\"name\" class=\"block text-sm font-medium text-gray-700\">Prénom</label>\n <input type=\"text\" name=\"name\" id=\"name\" autocomplete=\"given-name\" class=\"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\" value=\"{{ old('name', $user->name) }}\">\n @error('name')\n <span class=\"text-red-600\">{{ $message }}</span>\n @enderror\n </div>\n <div class=\"py-2\">\n <label for=\"email\" class=\"block text-sm font-medium text-gray-700\">Email</label>\n <input type=\"text\" name=\"email\" id=\"email\" autocomplete=\"given-name\" class=\"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\" value=\"{{ old('email', $user->email) }}\">\n @error('email')\n <span class=\"text-red-600\">{{ $message }}</span>\n @enderror\n </div>\n <div class=\"py-2\">\n <input type=\"submit\" class=\"cursor-pointer inline-flex items-center w-1/4 py-4 border border-gray-400 shadow-sm text-base font-medium rounded-md text-gray-700 bg-white justify-center\" value=\"Modifier\">\n </div>\n </div>\n </form>\n</div>\n@endsection","filename":"resources/views/users/edit.blade.php"}
Je comprends tout, enfin je crois... Si j'ai des questions je viens te chercher :p
Parfait ! Et bien voilà, on a fini le code des vues... Et on a aussi fini notre projet. Voyons dans la partie d'après quelques façons de l'améliorer et comment poursuivre ton apprentissage.
Ce qui a été vu dans cette partie :
J'ai terminé cette partie - La création d'un layout et son utilisation par l'héritage
- Quelques nouvelles fonctions de blade et des helpeurs
- Le cas particulier de la route logout POST