11 avril 2023   |   De Jérôme Borg   |    Laravel

Le Crud

Le Crud

<p>Le CRUD repr&eacute;sente toutes les op&eacute;rations que nous pouvons effectuer sur un mod&egrave;le.</p> <p>Cr&eacute;er :&nbsp;<span style="color: #ff0000;">C</span>reate or&nbsp;<span style="color: #ff0000;">A</span>dd<br>Lire :&nbsp;<span style="color: #ff0000;">R</span>ead or&nbsp;<span style="color: #ff0000;">Br</span>owse<br>Modifier :&nbsp;<span style="color: #ff0000;">U</span>pdate ou&nbsp;<span style="color: #ff0000;">E</span>dit<br>Supprimer :&nbsp;<span style="color: #ff0000;">D</span>elete</p> <p>Laravel, offre une fonctionnalit&eacute; sp&eacute;cifique pour le CRUD, les contr&ocirc;leurs de types ressources. Ce sont des contr&ocirc;leurs qui ont toutes les m&eacute;thodes n&eacute;cessaires pour le CRUD, &agrave; savoir 7</p> <p>index -&gt; liste tous les &eacute;l&eacute;ments<br>show -&gt; affiche un &eacute;l&eacute;ment<br>edit -&gt; renvoie vers un formulaire avec un &eacute;l&eacute;ment pour une modification<br>update -&gt; met &agrave; jour cette &eacute;l&eacute;ment<br>create&nbsp; -&gt; renvoie vers un formulaire vide pour une cr&eacute;ation<br>store -&gt; sauve cette &eacute;l&eacute;ment<br>delete -&gt; supprime cette &eacute;l&eacute;ment</p> <p>Pour les API, les m&eacute;thodes edit et create ne sont pas pr&eacute;sentes</p> <p>Nous allons cr&eacute;er un CRUD pour le mod&egrave;le Post</p> <pre class="language-bash"><code>php artisan make:model Post -mcrR</code></pre> <p>Grace a cette commande, on a cr&eacute;er le mod&egrave;le, sa migration -m, son contr&ocirc;leur -c, de type resource -r, et les forms Request -R</p> <p>Nous allons &eacute;galement ajouter les routes n&eacute;cessaires, dans ce cas de figure, il suffit d'en ajouter une, Laravel se charge du reste</p> <pre class="language-php"><code>Route::resource('/posts', PostController::class);</code></pre> <p>En effet, pour une route de type resource, Laravel cr&eacute;er automatiquent toutes les routes, bas&eacute;es sur les actions, et verbes, lancer la commande&nbsp;</p> <pre class="language-bash"><code>php artisan route:list</code></pre> <p><img src="../../../files/post-body-1737879580.png"></p> <p>Comme nous le voyons, chaque route est constitu&eacute;e d'une url et d'un verbe. Pour index et store, ce sont les m&ecirc;mes urls, mais par le m&ecirc;me verbe.</p> <p>A pr&eacute;sent, cr&eacute;ons le mod&egrave;le, pour la d&eacute;monstration, nous allons faire tr&egrave;s simple, un titre et une description,&nbsp;</p> <p>dans la migration database/migrations/Y_m_d_xxxxx_create_posts_table.php</p> <pre class="language-php"><code>Schema::create('posts', function (Blueprint $table) { $table-&gt;id(); $table-&gt;string('title'); $table-&gt;text('description'); $table-&gt;timestamps(); });</code></pre> <p>dans le mod&egrave;le app/Models/Post.php</p> <pre class="language-php"><code>protected $fillable = ['title' , 'description'];</code></pre> <p>dans le contr&ocirc;leur, app/Http/Controllers/PostController.php, pour la m&eacute;thode index, on r&eacute;cup&egrave;re tous les posts que l'on envoie a la vue index, dans le dossiers resources/views/posts</p> <pre class="language-php"><code>public function index() { $posts = Post::all(); return view('posts.index', compact('posts')); }</code></pre> <p>Afin de tester tous ceci, nous allons cr&eacute;er de fausses donn&eacute;es, commen&ccedil;ons par cr&eacute;er une factory</p> <pre class="language-bash"><code>php artisan make:factory PostFactory</code></pre> <pre class="language-php"><code>public function definition(): array { return [ 'title' =&gt; fake()-&gt;sentence(6, true), 'description' =&gt; fake()-&gt;paragraph( 3, true) ]; }</code></pre> <p>Laravel a int&eacute;gr&eacute; a ses d&eacute;pendances, depuis quelques versions, la biblioth&egrave;que de Francois Zaninotto, faker qui permet de cr&eacute;er du contenu qui a du sens.</p> <p>Lancons la migration,&nbsp;</p> <pre class="language-bash"><code>php artisan migrate</code></pre> <p>et injectons des donn&eacute;es, dans notre table posts, laravel propose un outil "tinker", qui permet d'&eacute;crire du code en ligne de commande</p> <pre class="language-php"><code>php artisan tinker App\Models\Post::factory(10)-&gt;create();</code></pre> <p>Ici on fait appel a la m&eacute;thode static factory du trait HasFactory, on lui passe en param&egrave;tre le nombre d'&eacute;l&eacute;ment &agrave; cr&eacute;er, puis la m&eacute;thode create, qui par d&eacute;faut va prendre les &eacute;l&eacute;ments d&eacute;finis dans PostFactory.</p> <p>On peut surcharger, comme par exemple</p> <pre class="language-php"><code>App\Models\Post::factory(10)-&gt;create(['title' =&gt; 'Un titre']);</code></pre> <p>Nous avons bien une table contenant 10 &eacute;l&eacute;ments</p> <p><img src="../../../files/post-body-1737880067.png"></p> <p>&nbsp;</p> <p><strong>index()</strong></p> <p>A pr&eacute;sent, passons a la vue index, dans le dossiers resources/views/posts/, cr&eacute;er un fichier index.blade.php, nous allons tous simplement lister les titres de post</p> <pre class="language-html"><code>&lt;table&gt; &lt;thead&gt; &lt;th&gt;Id&lt;/th&gt; &lt;th&gt;Title&lt;/th&gt; &lt;th&gt;Action&lt;/th&gt; &lt;/thead&gt; &lt;tbody&gt; @foreach($posts as $post) &lt;tr&gt; &lt;td&gt;{{ $post-&gt;id }}&lt;/td&gt; &lt;td&gt;{{ $post-&gt;title }}&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt; @endforeach &lt;/tbody&gt; &lt;/table&gt; </code></pre> <p>&nbsp;</p> <p><strong>show()</strong></p> <p>Passons a la m&eacute;thode show, modifions un peu le fichier index.blade.php</p> <pre class="language-html"><code> &lt;table&gt; &lt;thead&gt; &lt;th&gt;Id&lt;/th&gt; &lt;th&gt;Title&lt;/th&gt; &lt;th&gt;Action&lt;/th&gt; &lt;/thead&gt; &lt;tbody&gt; @foreach($posts as $post) &lt;tr&gt; &lt;td&gt;&lt;a href="{{ route('posts.show') }}" title="show"&gt;{{ $post-&gt;id }}&lt;/a&gt;&lt;/td&gt; &lt;td&gt;{{ $post-&gt;title }}&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt; @endforeach &lt;/tbody&gt; &lt;/table&gt;</code></pre> <p>En cliquant sur l'id, nous allons afficher l'&eacute;l&eacute;ment, pour cela, on passe dans l'url en get, le model, grace au "Model Binding", laravel va retrouver automatiquent l'id de l'&eacute;l&eacute;ment, et injecter dans le contr&ocirc;leur le mod&egrave;le directement. Concernant l'url, j'utilise les routes nomm&eacute;es, qui sont extr&ecirc;mement pratique, pour les obtenir, il faut lancer la commande php artisan route:list</p> <p>Dans la m&eacute;thode show du contr&ocirc;leur</p> <pre class="language-php"><code>public function show(Post $post) { return view('posts.show', compact('post')); }</code></pre> <p>Sans "model binding"</p> <pre class="language-php"><code>public function show($id) { $post = Post::findOrFail($id); return view('posts.show', compact('post')); }</code></pre> <p>Cr&eacute;ons &agrave; pr&eacute;sent la vue de consultation, resources/views/posts/show.blade.php</p> <pre class="language-html"><code>id: {{ $post-&gt;id }} title: {{ $post-&gt;title }} description: {{ $post-&gt;description }}</code></pre> <p>&nbsp;</p> <p><strong>create()</strong></p> <p>Pour la cr&eacute;ation d'un nouvel &eacute;l&eacute;ment, il faut un formulaire vide, et un lien nous renvoyant vers cette vue, dans index.blade.php</p> <p>rempla&ccedil;ons</p> <pre class="language-html"><code>&lt;th&gt;Action&lt;/th&gt;</code></pre> <p>par</p> <pre class="language-html"><code> &lt;th&gt;&lt;a href="{{ route('posts.create') }}" title="new"&gt;New&lt;/a&gt;&lt;/th&gt;</code></pre> <p>Dans la m&eacute;thode create() du controller</p> <pre class="language-php"><code>public function create() { return view('posts.createForm'); }</code></pre> <p>et enfin, notre formulaire vide, avec la gestion des erreurs</p> <pre class="language-html"><code>&lt;form action="{{ route('posts.store') }}" method="POST"&gt; @csrf &lt;div&gt; &lt;label for="title"&gt;Title&lt;/label&gt;&lt;br&gt; &lt;input type="text" name="title" id="title" value="{{ old('title','') }}"&gt; @error('title')&lt;br&gt;&lt;small style="color:#f00"&gt;{{ $message }}&lt;/small&gt;@enderror &lt;/div&gt; &lt;div&gt; &lt;label for="description"&gt;Description&lt;/label&gt;&lt;br&gt; &lt;textarea name="description" id="description"&gt;{{ old('description','') }}&lt;/textarea&gt; @error('description')&lt;br&gt;&lt;small style="color:#f00"&gt;{{ $message }}&lt;/small&gt;@enderror &lt;/div&gt; &lt;div&gt; &lt;button type="submit" name="Send"&gt;Send&lt;/button&gt; &lt;/div&gt; &lt;/form&gt;</code></pre> <p>C'est un simple formulaire html sans aucune class, ce n'est pas le sujet de cette article.</p> <p>&nbsp;</p> <p>On passe en route au formulaire, la route nomm&eacute;e vers la m&eacute;thode store, puis que le verbe est POST, et enfin le CSRF qui est un &eacute;l&eacute;ment indispensable de s&eacute;curit&eacute; pour laravel.</p> <p>Enfin pour chaque input, on peut faire appel a l'helper <strong>old</strong>, qui va afficher dans le champ la pr&eacute;c&eacute;dente saisie en cas d'erreur.</p> <p>On fait &eacute;galement appel a l'helper <strong>@error</strong> de blade, qui va afficher l'erreur trouv&eacute;e, si dans le tableau $errors, il trouve par exemple le champ 'title'</p> <p>&nbsp;</p> <p><strong>store()</strong></p> <p>A pr&eacute;sent dans la m&eacute;thode store du controller</p> <pre class="language-php"><code>public function store(StorePostRequest $request) { }</code></pre> <p>Laravel&nbsp; a directement inject&eacute; $request, qui contient toutes les variables de Post et Get, il sp&eacute;cifie &eacute;galement, qu'elles doivent &ecirc;tre de type StorePostRequest</p> <p>dans le fichier StorePostRequest, commen&ccedil;ons par autoris&eacute; l'utilisation</p> <pre class="language-php"><code>public function authorize(): bool { return true; }</code></pre> <p>puis &eacute;crivons nos r&egrave;gles de validation (<a href="https://www.the-blob.io/posts/la-validation" target="_blank" rel="noopener">voir article sur la validation</a>)</p> <pre class="language-php"><code>public function rules(): array { return [ 'title' =&gt; 'required|string|max:255', 'description' =&gt; 'required|string|max:5000', ]; }</code></pre> <p>On renvoie un tableau associatif, ayant pour cl&eacute; le nom du champ a valider, et pour valeur un tableau de r&egrave;gles de validation, comme par exemple pour "title", "requis", "chaine de caract&egrave;re" et "maximum 255 caract&egrave;res". Lorsque que l'on soumet le formulaire, il va &ecirc;tre envoy&eacute; a cette class de validation, qui va v&eacute;rifier les r&egrave;gles de validation. Si une ou plusieurs r&egrave;gles ne sont pas respect&eacute;es, il renvoie &agrave; la vue, avec les donn&eacute;es saisies et un tableau d'erreur.</p> <p><img src="../../../files/post-body-1737880948.png"></p> <p>si par contre les donn&eacute;es, sont valid&eacute;es, elles seront retourn&eacute;es gr&acirc;ce &agrave; la m&eacute;thode <strong>$request-&gt;validated()</strong>, et nous pourrons proc&eacute;der a l'enregistrement</p> <pre class="language-php"><code>public function store(StorePostRequest $request) { Post::create($request-&gt;validated()); return redirect()-&gt;route('posts.index'); }</code></pre> <p>Puis nous r&eacute;digeons l'utilisateur vers la page d'index, ou se trouve notre nouveau Post</p> <p>&nbsp;</p> <p><strong>edit()</strong></p> <p>Passons &agrave; pr&eacute;sent a l'&eacute;dition, pour cela modifions un peu notre fichier index.blade.php, dans la colonne en dessous de new, rajoutons un bouton permettant d'&eacute;diter notre mod&egrave;le</p> <pre class="language-html"><code>&lt;td&gt;&lt;a href="{{ route('posts.edit') }}" title="edit"&gt;Edit&lt;/a&gt;&lt;/td&gt;</code></pre> <p>Dans la m&eacute;thode edit du controller</p> <pre class="language-php"><code>public function edit(Post $post) { return view('posts.editForm', compact('post')); }</code></pre> <p>Cr&eacute;ons le formulaire, nous allons copier/coller createForm.blade.php en editForm.blade.php, et le modifier</p> <pre class="language-html"><code> &lt;form action="{{ route('posts.update',$post) }}" method="POST"&gt; @method('PUT') @csrf &lt;div&gt; &lt;label for="title"&gt;Title&lt;/label&gt;&lt;br&gt; &lt;input type="text" name="title" id="title" value="{{ old('title',$post-&gt;title) }}"&gt; @error('title')&lt;br&gt;&lt;small style="color:#f00"&gt;{{ $message }}&lt;/small&gt;@enderror &lt;/div&gt; &lt;div&gt; &lt;label for="description"&gt;Description&lt;/label&gt;&lt;br&gt; &lt;textarea name="description" id="description"&gt;{{ old('description',$post-&gt;description) }}&lt;/textarea&gt; @error('description')&lt;br&gt;&lt;small style="color:#f00"&gt;{{ $message }}&lt;/small&gt;@enderror &lt;/div&gt; &lt;div&gt; &lt;button type="submit" name="Send"&gt;Send&lt;/button&gt; &lt;/div&gt; &lt;/form&gt;</code></pre> <p>La route a chang&eacute;, de plus on lui passe en param&egrave;tre le mod&egrave;le, le verbe impos&eacute; pour de l'update est PUT ou PATCH, on applique donc la m&eacute;thode souhait&eacute;e avec directive @method('PUT')</p> <p>pour les inputs, si on a la valeur, on l'affiche "$post-&gt;title"</p> <p>&nbsp;</p> <p><strong>update()</strong></p> <p>dans le contr&ocirc;leur, commen&ccedil;ons par la form Request, ce sont les m&ecirc;me r&egrave;gles que pour la cr&eacute;ation, et pensons a autoriser l'utilisation</p> <pre class="language-php"><code>public function rules(): array { return [ 'title' =&gt; 'required|string|max:255', 'description' =&gt; 'required|string|max:5000', ]; }</code></pre> <pre class="language-php"><code>public function update(UpdatePostRequest $request, Post $post) { $post-&gt;update($request-&gt;validated()); return redirect()-&gt;route('posts.index'); }</code></pre> <p>Comme pour la cr&eacute;ation,&nbsp; si les donn&eacute;es sont valid&eacute;es, elles seront retourn&eacute;es gr&acirc;ce &agrave; la m&eacute;thode $request-&gt;validated(), et nous pouvons proc&eacute;der a la mise &agrave; jour</p> <p>&nbsp;</p> <p><strong>delete()</strong></p> <p>Enfin passons &agrave; la suppression, nous allons ajouter un bouton de suppression dans l'index. Comme le verbe impos&eacute; est "delete", nous allons devoir pass&eacute; par un formulaire. De plus lors d'actions "potentiellement risqu&eacute;es", il est bien vu de demander une confirmation &agrave; l'utilisateur, ajoutons ce code dans la page index.blade.php a cot&eacute; de Edit</p> <pre class="language-html"><code>&lt;td&gt;&lt;a href="{{ route('posts.edit') }}" title="edit"&gt;Edit&lt;/a&gt; &lt;a href="" title="delete" onclick="event.preventDefault();let response = confirm('Are You Sure?'); if (response) { document.getElementById('delete{{ $post-&gt;id }}').submit() }"&gt;Delete&lt;/a&gt; &lt;form id="delete{{ $post-&gt;id }}" action="{{ route('posts.destroy') }}" method="POST"&gt; @csrf @method('DELETE') &lt;/form&gt; &lt;/td&gt;</code></pre> <p>Via un href, et javascript confirm, on demande a l'utilisateur s'il valide la suppression, On stocke sa r&eacute;ponse dans response, si oui on recherche le formulaire de cet &eacute;l&eacute;ment avec document.getElementById('delete{{ $post-&gt;id }}'), et on appelle sa m&eacute;thode submit, ce qui va appeler la m&eacute;thode destroy de notre contr&ocirc;leur</p> <pre class="language-php"><code>public function destroy(Post $post) { $post-&gt;delete(); return redirect()-&gt;route('posts.index'); }</code></pre> <p>enfin on fait appel a la m&eacute;thode delete de la class Model, et on redirige vers l'index</p> <p><strong>Nous venons de d&eacute;velopper un CRUD pour le mod&egrave;le Post</strong></p> <p>&nbsp;</p>
De Jérôme Borg
Le 11 avril 2023
Temps de lecture : 21 min
Jérôme Borg
Jérôme Borg

Développeur fullstack laravel/VueJs, formateur

Tous les articles de cet auteur
Articles recommandés
Le fichier d'environnement .env
Le fichier d'environnement .env
Jérôme Borg De Jérôme Borg | 23 février 2023 | Lu : 5min
Homestead
Homestead
Jérôme Borg De Jérôme Borg | 20 avril 2023 | Lu : 10min