Le Crud
Le CRUD représente toutes les opérations que nous pouvons effectuer sur un modèle.
Créer : Create or Add
Lire : Read or Browse
Modifier : Update ou Edit
Supprimer : Delete
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
index -> liste tous les éléments
show -> affiche un élément
edit -> renvoie vers un formulaire avec un élément pour une modification
update -> met à jour cette élément
create -> renvoie vers un formulaire vide pour une création
store -> sauve cette élément
delete -> supprime cette élément
Pour les API, les méthodes edit et create ne sont pas présentes
Nous allons créer un CRUD pour le modèle Post
php artisan make:model Post -mcrR
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
Nous allons également ajouter les routes nécessaires, dans ce cas de figure, il suffit d'en ajouter une, Laravel se charge du reste
Route::resource('/posts', PostController::class);
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
php artisan route:list
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.
A présent, créons le modèle, pour la démonstration, nous allons faire très simple, un titre et une description,
dans la migration database/migrations/Y_m_d_xxxxx_create_posts_table.php
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->timestamps();
});
dans le modèle app/Models/Post.php
protected $fillable = ['title' , 'description'];
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
public function index()
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
Afin de tester tous ceci, nous allons créer de fausses données, commençons par créer une factory
php artisan make:factory PostFactory
public function definition(): array {
return [
'title' => fake()->sentence(6, true),
'description' => fake()->paragraph( 3, true)
];
}
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.
Lancons la migration,
php artisan migrate
et injectons des données, dans notre table posts, laravel propose un outil "tinker", qui permet d'écrire du code en ligne de commande
php artisan tinker
App\Models\Post::factory(10)->create();
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.
On peut surcharger, comme par exemple
App\Models\Post::factory(10)->create(['title' => 'Un titre']);
Nous avons bien une table contenant 10 éléments
index()
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
<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>
show()
Passons a la méthode show, modifions un peu le fichier index.blade.php
<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>
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
Dans la méthode show du contrôleur
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
Sans "model binding"
public function show($id)
{
$post = Post::findOrFail($id);
return view('posts.show', compact('post'));
}
Créons à présent la vue de consultation, resources/views/posts/show.blade.php
id: {{ $post->id }}
title: {{ $post->title }}
description: {{ $post->description }}
create()
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
remplaçons
<th>Action</th>
par
<th><a href="{{ route('posts.create') }}" title="new">New</a></th>
Dans la méthode create() du controller
public function create()
{
return view('posts.createForm');
}
et enfin, notre formulaire vide, avec la gestion des erreurs
<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>
C'est un simple formulaire html sans aucune class, ce n'est pas le sujet de cette article.
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.
Enfin pour chaque input, on peut faire appel a l'helper old, qui va afficher dans le champ la précédente saisie en cas d'erreur.
On fait également appel a l'helper @error de blade, qui va afficher l'erreur trouvée, si dans le tableau $errors, il trouve par exemple le champ 'title'
store()
A présent dans la méthode store du controller
public function store(StorePostRequest $request)
{
}
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
dans le fichier StorePostRequest, commençons par autorisé l'utilisation
public function authorize(): bool
{
return true;
}
puis écrivons nos règles de validation (voir article sur la validation)
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'description' => 'required|string|max:5000',
];
}
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.
si par contre les données, sont validées, elles seront retournées grâce à la méthode $request->validated(), et nous pourrons procéder a l'enregistrement
public function store(StorePostRequest $request)
{
Post::create($request->validated());
return redirect()->route('posts.index');
}
Puis nous rédigeons l'utilisateur vers la page d'index, ou se trouve notre nouveau Post
edit()
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
<td><a href="{{ route('posts.edit') }}" title="edit">Edit</a></td>
Dans la méthode edit du controller
public function edit(Post $post)
{
return view('posts.editForm', compact('post'));
}
Créons le formulaire, nous allons copier/coller createForm.blade.php en editForm.blade.php, et le modifier
<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>
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')
pour les inputs, si on a la valeur, on l'affiche "$post->title"
update()
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
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'description' => 'required|string|max:5000',
];
}
public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());
return redirect()->route('posts.index');
}
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
delete()
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
<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>
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
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index');
}
enfin on fait appel a la méthode delete de la class Model, et on redirige vers l'index
Nous venons de développer un CRUD pour le modèle Post
Le 11 avril 2023
Temps de lecture : 21 min