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