Créer une application avec Laravel 5.5 – Les catégories 1/2
On va poursuivre la création de notre galerie photos en nous intéressant dans ce chapitre aux catégories. En effet pour organiser un peu les photos de la galerie on les classe en catégories. Seul un administrateur peut créer, modifier ou supprimer une catégorie.
On va voir dans ce chapitre comment on crée une catégorie. Pour ça on va devoir déjà compléter le menu de la barre de navigation, créer un formulaire, les routes, le contrôleur et même un middleware et un événement…
Un middleware
Pour vérifier qu’on a affaire à un administrateur le plus simple est de créer un middleware :
php artisan make:middleware Admin
On va changer ainsi la fonction handle :
public function handle($request, Closure $next) { $user = $request->user(); if ($user && $user->role === 'admin') { return $next($request); } return redirect()->route('home'); }
Si c’est un administrateur on continue, sinon on renvoie sur la page d’accueil.
On le déclare dans app/Http/Kernel.php :
protected $routeMiddleware = [ ... 'admin' => \App\Http\Middleware\Admin::class, ];
On va en profiter pour ajouter une directive à Blade dans AppServiceProvider :
use Illuminate\Support\Facades\Blade; ... public function boot() { Blade::if('admin', function () { return auth()->check() && auth()->user()->role === 'admin'; }); }
On pourra ainsi écrire @admin dans les vues !
Le contrôleur
Pour gérer les catégories on va créer un contrôleur :
php artisan make:controller CategoryController --resource
Le fait d’utiliser l’option –resource a généré les 7 méthodes de base. On va toutes les conserver sauf show.
Les routes
Pour les routes on va ajouter ça :
Route::middleware('admin')->group(function () { Route::resource ('category', 'CategoryController', [ 'except' => 'show' ]); });
Le groupe nous servira plus tard quand on ajoutera d’autres routes.
On vérifie :
php artisan route:list
On a bien nos 6 routes pour notre contrôleur.
Le menu
Pour accéder au formulaire de création d’une catégorie on va devoir compléter la barre de navigation dans views/layouts/app :
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="{{ route('home') }}">{{ config('app.name', 'Album') }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle{{ currentRoute( route('category.create') )}}" href="#" id="navbarDropdownGestCat" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @lang('Administration') </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownGestCat"> <a class="dropdown-item" href="{{ route('category.create') }}"> <i class="fas fa-plus fa-lg"></i> @lang('Ajouter une catégorie') </a> </div> </li> </ul> <ul class="navbar-nav ml-auto"> @guest <li class="nav-item{{ currentRoute(route('login')) }}"><a class="nav-link" href="{{ route('login') }}">@lang('Connexion')</a></li> <li class="nav-item{{ currentRoute(route('register')) }}"><a class="nav-link" href="{{ route('register') }}">@lang('Inscription')</a></li> @else <li class="nav-item"> <a id="logout" class="nav-link" href="{{ route('logout') }}">@lang('Déconnexion')</a> <form id="logout-form" action="{{ route('logout') }}" method="POST" class="hide"> {{ csrf_field() }} </form> </li> @endguest </ul> </div> </nav>
La partie ajoutée est celle-ci :
@admin <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle{{ currentRoute( route('category.create') )}}" href="#" id="navbarDropdownGestCat" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @lang('Administration') </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownGestCat"> <a class="dropdown-item" href="{{ route('category.create') }}"> <i class="fa fa-plus fa-lg"></i> @lang('Ajouter une catégorie') </a> </div> </li> @endadmin
Si on a affaire à un administrateur (@admin) on crée un menu déroulant. Pour le moment on déroule un seul item mais on complétera plus tard.
Dans le code j’ai prévu une icône de Font Awesome mais elle n’apparaît pas parce qu’on n’a pas chargé la librairie ni les icônes. La librairie est passée en version 5 avec des icônes en SVG. Le plus simple est d’utiliser le CDN dans le header :
<head> ... <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script> </head>
Et voilà une icône dans le menu déroulant :
La vue de création
On crée un dossier pour les catégories et une vue pour la création :
Pour cette vue on utilise l’intendance qu’on a précédemment mise en place :
@extends('layouts.form') @section('card') @component('components.card') @slot('title') @lang('Ajouter une catégorie') @endslot <form method="POST" action="{{ route('category.store') }}"> {{ csrf_field() }} @include('partials.form-group', [ 'title' => __('Nom'), 'type' => 'text', 'name' => 'name', 'required' => true, ]) @component('components.button') @lang('Envoyer') @endcomponent </form> @endcomponent @endsection
L’affichage du formulaire
Il nous faut maintenant coder la gestion de tout ça dans le contrôleur CategoryController.
Déjà il faut afficher le formulaire :
public function create() { return view('categories.create'); }
On vérifie avec le menu que ça marche (il faut se connecter comme administrateur) :
La validation
Pour la validation on crée une requête de formulaire :
php artisan make:request CategoryRequest
Et on change ainsi le code :
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CategoryRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { $id = $this->category ? ',' . $this->category->id : ''; return $rules = [ 'name' => 'required|string|max:255|unique:categories,name' . $id, ]; } }
C’est déjà préparé pour gérer la validation de la modification. Dans ce cas on sait qu’il faut arranger un peu la règle d’unicité.
La soumission
A la soumission on arrive dans la méthode store du contrôleur. On va la coder ainsi :
use App\Http\Requests\CategoryRequest; use App\Models\Category; ... public function store(CategoryRequest $request) { Category::create($request->all()); return redirect()->route('home')->with('ok', __('La catégorie a bien été enregistrée')); }
Mais évidemment ça ne va pas encore fonctionner :
En effet on a pas donné de valeur au slug. Comme on va en avoir besoin dans le cas de la création et de la modification on va se servir d’un événement.
Un événement
On crée le listener :
php artisan make:listener CategorySaving
Et l’événement :
php artisan make:event CategorySaving
Dans l’événement on se contente de transmettre le modèle :
<?php namespace App\Events; use Illuminate\ { Queue\SerializesModels, Database\Eloquent\Model }; class CategorySaving { use SerializesModels; public $model; public function __construct(Model $model) { $this->model = $model; } }
Et dans le listener on code le slug :
<?php namespace App\Listeners; use App\Events\CategorySaving as EventCategorySaving; class CategorySaving { public function handle(EventCategorySaving $event) { $event->model->slug = str_slug($event->model->name, '-'); } }
Merci à l’helper str_slug de Laravel !
On établit la liaison entre event et listener dans EventServiceProvider :
protected $listen = [ 'App\Events\CategorySaving' => [ 'App\Listeners\CategorySaving', ], ];
Il ne reste plus qu’à propager l’événement à partir du modèle Category :
use App\Events\CategorySaving; ... protected $dispatchesEvents = [ 'saving' => CategorySaving::class, ];
Maintenant la sauvegarde devrait fonctionner !
Le message
Dans le contrôleur on renvoie une information de réussite en flash session. Il faut prévoir dans le layout (views/layouts/app) de quoi l’afficher :
@if (session('ok')) <div class="container"> <div class="alert alert-dismissible alert-success fade show" role="alert"> {{ session('ok') }} <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> </div> @endif @yield('content')
Et maintenant ça doit fonctionner :
Conclusion
Dans ce chapitre on a :
- créé un middleware pour l’administration
- créé une directive Blade
- créé routes, contrôleur et requête de formulaire pour la création d’un catégorie
- complété la barre de navigation
- créé un événement pour la création du slug
- modifié le layout pour afficher un message
Pour vous simplifier la vie vous pouvez charger le projet dans son état à l’issue de ce chapitre.
24 commentaires
JeanPierre68
Bonsoir, j’ai voulu reproduire le tuto Album de la version 5.5 avec la version 6.0.
Je reste coincé car str_slug dans CategorySaving.php n’est pas reconnu.
j’ai essayer avec str::slug comme c’est écris dans certain forum mais aucun résultat.
Merci pour votre aide.
Jean-Pierre
bestmomo
Salut,
Il faut utiliser
Str::slug
en déclarantuse Illuminate\Support\Str;
JeanPierre68
Merci pour la réponse, je vais essayer dans la soirée.
Et encore merci a toutes ces personnes qui créer ces tuto sympas
Bonne journée Jean-Pierre
yakaman
Bonjour,
Le helper currentRoute retourne class=current pour les liens actifs , or dans le template Blade on place ça au sein d’une déclaration de classe css: class= »nav-link dropdown-toggle{{ currentRoute( route(‘category.create’) )}} »
Ca ne pose pas problème ?
Un grand merci pour ces guides, le meilleur site francophone sur Laravel !
bestmomo
Bonjour,
L’helper currentRoute retourne » active ».
yakaman
Je viens de me rendre compte que j’étais sur la base de code de l’application d’exemple précédente.
Shame on me…
zb2oby
Bonjour,
Même problème..
il semblerais que le JS executé provienne en fait de la debug bar.. étrangement cette erreur n’apparaît que lors de la soumission du formulaire. Avant cela la debugbar fonctionne correctement et l’inspecteur ne renvoie pas d’erreur.
Uncaught TypeError: Cannot read property ‘style’ of undefined
at Sa (http://../album/public/_debugbar/assets/javascript?v=1519565518:3:27454)
at Function.css (http://../album/public/_debugbar/assets/javascript?v=1519565518:3:30895)
at La (http://../album/public/_debugbar/assets/javascript?v=1519565518:3:24665)
at Ma (http://../album/public/_debugbar/assets/javascript?v=1519565518:3:24749)
at cb (http://../album/public/_debugbar/assets/javascript?v=1519565518:3:28868)
at n.fn.init.show (http://../album/public/_debugbar/assets/javascript?v=1519565518:4:578)
at n.fn.init.n.fn.(anonymous function) [as show] (http://../album/public/_debugbar/assets/javascript?v=1519565518:4:7543)
at child.setOpenHandler (http://../laravel/album/public/_debugbar/assets/javascript?v=1519565518:999:31)
at http://../album/public/category:48:13
J’ai vérifié ce que tu dit de vérifier :
– pour la génération depuis npm apres un « npm run dev » il semble bien être généré par celui-ci
– il est bien déclaré dans le layout : http://%20asset('js/app.js')%20
– en revanche de quelles 3 librairies necessaires parles-tu ?
J’ai testé en desactivant JS donc évidemment plus de problème d’erreur JS mais pas de mise à jour de la BDD avec ma nouvelle catégorie..
EDIT :
Du coup : en reprenant la page de tuto depuis le début j’ai remarqué que j’avais mal mis a jour le contrôleur. Une fois cette modification bien effectuée l’enregistrement se passe très bien et plus du tout d’erreur JS
mrhili
what is that __(‘La catégorie a bien été enregistrée’)
bestmomo
It’s an helper : https://laravel.com/docs/5.5/localization#retrieving-translation-strings
mrhili
what is that line
$id = $this->category ? ‘,’ . $this->category->id : »;
bestmomo
Validation is used for creation and edition, is this last case we need to exclude the actual record when checking unique rule.
mrhili
How did you learn this mooove
use Illuminate\Support\Facades\Blade;
…
public function boot()
{
Blade::if(‘admin’, function () {
return auth()->check() && auth()->user()->role === ‘admin’;
});
}
bestmomo
Hello,
https://laravel.com/docs/5.5/blade#custom-if-statements
g_bu
J’ai réussi à régler le problème:
1. je me suis rendu sur un bootstrap starter template (https://getbootstrap.com/docs/4.0/examples/starter-template/)
2. j’ai récupérer dans le code les .js et je les ai téléchargés
3. je les ai introduits dans le webpack.mix.js dans le même ordre que dans le template:
‘resources/assets/js/jquery-3.2.1.slim.min.js’,
‘resources/assets/js/popper.min.js’,
‘resources/assets/js/bootstrap.min.js’,
Il doit y avoir un problème entre certaines versions beta du boostrap 4 et de popper… d’ailleurs j’ai toujours une erreur que je ne comprend pas (mais elle n’a pas d’influence visible… pour l’instant):
Erreur dans les liens source : request failed with status 404
URL de la ressource : http://phonemia.oo/js/app.js
URL du lien source : bootstrap.min.js.map
Mais je ne suis apparemment pas le seul: https://github.com/twbs/bootstrap/issues/23381
En espérant que cela puisse aider quelqu’un!
bestmomo
Salut,
Merci pour la description détaillée. Le souci vient du fait que lorsque j’ai rédigé l’article, et donc écrit le code, Bootstrap en était à la version beta2 et que maintenant il en est à la beta3. Donc pour suivre le tuto je pense que le plus simple est d’utiliser la beta2 parce sinon il doit y avoir des incohérence au niveau du balisage et peut-être aussi avec popper. Lorsque Bootstrap passera en version définitive, ce qui ne saurait tarder, je reprendrai le tuto pour que ça fonctionne mais je dois tout repasser en revue parce que j’ai mis pour chaque article un ZIP à charger…
g_bu
oui c’est un boulot incroyable que tu as fait avec les versions intermédiaires en ZIP.
Encore merci!!!
bestmomo
Bonjour,
J’ai créé le code avec la version beta2 de Bootstrap qui en est maintenant à la beta3, il y a donc peut-être un souci si vous utilisez cette version par rapport au code que j’ai prévu dans les vues.
lauraneblettery
Bonjour, j’ai terminé mais quand je clique sur envoyer pour ajouter la catégorie ca me donne une page blanche avec cette erreur : javascript?v=1505741566:3 Uncaught TypeError: Cannot read property ‘style’ of undefined
bestmomo
Bonjour,
C’est étrange cette erreur parce que le formulaire n’utilise pas de Javascript… Vérifie bien le code.
Kikiro
Salut BESTMOMO
Mille merci pour ce cours qui m’apprend beaucoup.
Le code fonctionne parfaitement mais je n’arrive pas à obtenir le Dropdown du menu « Ajouter une catégorie ».
je ne vos que « Administration » et lorsque je clic rien ne fait Dropdown.
J’ai même copier le code source de ton fichier « views/layouts/app » et aussi le contenu de « resources\assets » puis « npm run dev » mais c’est pareille
Merci de m’aider
bestmomo
Bonjour,
Il faudrait vérifier que le fichier public/js/app.js est bien généré par npm et qu’il comporte bien les 3 librairies nécessaires. D’autre part vérifier aussi qu’il est bien déclaré dans le layout. Pour la forme vérifier aussi dans un autre navigateur des fois qu’il y ait un souci avec Javascript…
Kikiro
Bonjour
Merci c’est trouvé.
Vraiment cool, tu es !
manuel agnero
Salut j’ai le même soucis par contre je ne retrouve pas la source du problème
g_bu
Salut,
Un énorme merci pour ces précieuses ressources!!! Je débute avec Laravel et c’est vraiment très utile.
J’ai également le même problème… je n’ai pas encore trouvé la solution. Apparemment c’est quelque chose avec Popper.js..!? peut-être à cet endroit-là: export default Popper;
C’est la seule erreur indiquée par la console.
D’ailleurs le menu en responsive ne fonctionne pas (l’icône du menu « smartphone » s’affiche mais rien ne se passe lorsque je clic dessus).
@kikiro : qu’as-tu fait pour que ça fonctionne?