
Cours Laravel 8 – les données – les ressources (2/2)
Dans cet article nous allons terminer de coder la ressource que nous avons commencée dans le précédent. Il nous reste à voir comment créer et modifier un film.
Créer un film
Les routes
Pour la création d’un film on va avoir deux routes :
- pour afficher le formulaire de création
- pour soumettre le formulaire
Le contrôleur
Dans le contrôleur ce sont les méthodes create et store qui sont concernées. On va donc les coder :
use App\Http\Requests\Film as FilmRequest; ... public function create() { return view('create'); } public function store(FilmRequest $filmRequest) { Film::create($filmRequest->all()); return redirect()->route('films.index')->with('info', 'Le film a bien été créé'); }
La vue index
On a déjà codé la vue index mais on n’a pas prévu de bouton pour créer un film. Alors on va arranger ça (je remets tout le code pour que ce soit plus simple) :
@extends('template') @section('css') <style> .card-footer { justify-content: center; align-items: center; padding: 0.4em; } .is-info { margin: 0.3em; } </style> @endsection @section('content') @if(session()->has('info')) <div class="notification is-success"> {{ session('info') }} </div> @endif <div class="card"> <header class="card-header"> <p class="card-header-title">Films</p> <a class="button is-info" href="{{ route('films.create') }}">Créer un film</a> </header> <div class="card-content"> <div class="content"> <table class="table is-hoverable"> <thead> <tr> <th>#</th> <th>Titre</th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> @foreach($films as $film) <tr> <td>{{ $film->id }}</td> <td><strong>{{ $film->title }}</strong></td> <td><a class="button is-primary" href="{{ route('films.show', $film->id) }}">Voir</a></td> <td><a class="button is-warning" href="{{ route('films.edit', $film->id) }}">Modifier</a></td> <td> <form action="{{ route('films.destroy', $film->id) }}" method="post"> @csrf @method('DELETE') <button class="button is-danger" type="submit">Supprimer</button> </form> </td> </tr> @endforeach </tbody> </table> </div> </div> <footer class="card-footer is-centered"> {{ $films->links() }} </footer> </div> @endsection
Maintenant on a un bouton :
La vue create
On crée la vue create :
@extends('template') @section('content') <div class="card"> <header class="card-header"> <p class="card-header-title">Création d'un film</p> </header> <div class="card-content"> <div class="content"> <form action="{{ route('films.store') }}" method="POST"> @csrf <div class="field"> <label class="label">Titre</label> <div class="control"> <input class="input @error('title') is-danger @enderror" type="text" name="title" value="{{ old('title') }}" placeholder="Titre du film"> </div> @error('title') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <label class="label">Année de diffusion</label> <div class="control"> <input class="input" type="number" name="year" value="{{ old('year') }}" min="1950" max="{{ date('Y') }}"> </div> @error('year') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <label class="label">Description</label> <div class="control"> <textarea class="textarea" name="description" placeholder="Description du film">{{ old('description') }}</textarea> </div> @error('description') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <div class="control"> <button class="button is-link">Envoyer</button> </div> </div> </form> </div> </div> </div> @endsection
Comme on a prévu la validation on va vérifier que ça marche :
Lorsqu’un film a été créé on retourne à la vue index avec un message :
Modifier un film
Les routes
Pour la modification d’un film on va avoir deux routes :
- pour afficher le formulaire de modification
- pour soumettre le formulaire
Le contrôleur
Dans le contrôleur ce sont les méthodes edit et update qui sont concernées. On va donc les coder :
public function edit(Film $film) { return view('edit', compact('film')); } public function update(FilmRequest $filmRequest, Film $film) { $film->update($filmRequest->all()); return redirect()->route('films.index')->with('info', 'Le film a bien été modifié'); }
Le code ressemble beaucoup à celui vu ci-dessus pour la création et c’est normal.
La vue edit
Là aussi on va avoir pratiquement le même code que la vue create. La différence c’est qu’il faut renseigner les contrôles du formulaire au départ. On crée la vue edit :
@extends('template') @section('content') <div class="card"> <header class="card-header"> <p class="card-header-title">Modification d'un film</p> </header> <div class="card-content"> <div class="content"> <form action="{{ route('films.update', $film->id) }}" method="POST"> @csrf @method('put') <div class="field"> <label class="label">Titre</label> <div class="control"> <input class="input @error('title') is-danger @enderror" type="text" name="title" value="{{ old('title', $film->title) }}" placeholder="Titre du film"> </div> @error('title') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <label class="label">Année de diffusion</label> <div class="control"> <input class="input" type="number" name="year" value="{{ old('year', $film->year) }}" min="1950" max="{{ date('Y') }}"> </div> @error('year') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <label class="label">Description</label> <div class="control"> <textarea class="textarea" name="description" placeholder="Description du film">{{ old('description', $film->description) }}</textarea> </div> @error('description') <p class="help is-danger">{{ $message }}</p> @enderror </div> <div class="field"> <div class="control"> <button class="button is-link">Envoyer</button> </div> </div> </form> </div> </div> </div> @endsection
Là encore on a la validation qui fonctionne et le message qui informe de la modification avec la redirection :
Ordonner les films
Pour le moment les films sont listés dans l’ordre de leur identifiant, ce qui n’est pas très heureux. Il serait plus judicieux d’avoir la liste dans l’ordre alphabétique des noms de films. Pour le faire il suffit d’intervenir dans le contrôleur :
public function index() { $films = Film::oldest('title')->paginate(5);
Maintenant on a bien les films dans l’ordre désiré :
Finalement aussi quel intérêt de faire apparaître l’identifiant ? Surtout maintenant qu’ils sont dans le désordre, on va donc modifier la vue index :
<thead> <tr> <th>Titre</th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> @foreach($films as $film) <tr> <td><strong>{{ $film->title }}</strong></td> <td><a class="button is-primary" href="{{ route('films.show', $film->id) }}">Voir</a></td> <td><a class="button is-warning" href="{{ route('films.edit', $film->id) }}">Modifier</a></td> <td> <form action="{{ route('films.destroy', $film->id) }}" method="post"> @csrf @method('DELETE') <button class="button is-danger" type="submit">Supprimer</button> </form> </td> </tr> @endforeach </tbody>
C’est beaucoup mieux :
Soft delete
Telle qu’on a prévue la suppression des films ils sont définitivement retirés de la base. Laravel propose une autre façon de procéder avec le soft delete. Dans un premier temps on se contente de marquer le film comme supprimé en renseignant une colonne deleted_at. Dans un deuxième temps on peut faire la suppression définitive. En gros on crée une corbeille.
La migration
Dans la migration de la table films on ajoute la colonne avec la méthode softDeletes :
public function up() { Schema::create('films', function (Blueprint $table) { ... $table->softDeletes(); }); }
On relance la migration avec la population :
php artisan migrate:fresh --seed
Le modèle
Au niveau du modèle Film on ajoute le trait SoftDeletes :
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Film extends Model { use HasFactory, SoftDeletes; protected $fillable = ['title', 'year', 'description']; }
Maintenant quand on supprime un film on le conserve dans la base et on renseigne la colonne deleted_at :
Les routes
On ajoute deux routes pour les deux nouvelles actions :
Route::delete('films/force/{film}', [FilmController::class, 'forceDestroy'])->name('films.force.destroy'); Route::put('films/restore/{film}', [FilmController::class, 'restore'])->name('films.restore');
Le contrôleur
Dans le contrôleur il faut déjà modifier la méthode index pour inclure les films de la corbeille :
public function index() { $films = Film::withTrashed()->oldest('title')->paginate(5); return view('index', compact('films')); }
Dans la méthode destroy il faut changer le texte du message :
return back()->with('info', 'Le film a bien été mis dans la corbeille.');
Enfin il faut créer les deux nouvelles méthodes :
public function forceDestroy($id) { Film::withTrashed()->whereId($id)->firstOrFail()->forceDelete(); return back()->with('info', 'Le film a bien été supprimé définitivement dans la base de données.'); } public function restore($id) { Film::withTrashed()->whereId($id)->firstOrFail()->restore(); return back()->with('info', 'Le film a bien été restauré.'); }
La vue index
Il ne reste plus qu’à modifier la vue index :
@foreach($films as $film) <tr @if($film->deleted_at) class="has-background-grey-lighter" @endif> <td><strong>{{ $film->title }}</strong></td> <td> @if($film->deleted_at) <form action="{{ route('films.restore', $film->id) }}" method="post"> @csrf @method('PUT') <button class="button is-primary" type="submit">Restaurer</button> </form> @else <a class="button is-primary" href="{{ route('films.show', $film->id) }}">Voir</a> @endif </td> <td> @if($film->deleted_at) @else <a class="button is-warning" href="{{ route('films.edit', $film->id) }}">Modifier</a> @endif </td> <td> <form action="{{ route($film->deleted_at? 'films.force.destroy' : 'films.destroy', $film->id) }}" method="post"> @csrf @method('DELETE') <button class="button is-danger" type="submit">Supprimer</button> </form> </td> </tr> @endforeach
Fonctionnement
Maintenant quand on clique sur le bouton Supprimer d’un film ça le met dans la corbeille et il prend cette apparence avec un fond grisé et des boutons différents :
On peut alors soit le supprimer définitivement avec le bouton Supprimer, soit le restaurer avec le bouton Restaurer.
J’ai prévu un ZIP récupérable ici qui contient le projet complet pour les deux articles sur les ressources.
En résumé
- Un contrôleur de ressource permet de normaliser les opérations CRUD. Sa création et son routage sont simples.
- Le soft delete permet de mettre en place une corbeille pour les enregistrements.


12 commentaires
elProfessor
Bel article. Très instructif. Merci beaucoup et force à vous !
bestmomo
Merci !
delacoche
merci , bien resolu
delacoche
cc besoin d’aide svp , l’ors de l’utilisation de ma route : /edit j’ai une erreur :
Missing required parameters for [Route: taches.update] [URI: taches/{tach}]. (View: /Users/delacochesilver/XAMPP/xamppfiles/htdocs/chosesafaire/resources/views/edit.blade.php)
delacoche
mon conroller
validate(
[
'titre' => 'required|max:100',
'detail'=> 'required|max: 500',
]
);
$tache = new tache;
$tache->titre = $request->titre;
$tache->detail = $request->detail;
$tache->save();
return redirect()->route('taches.index')->with('message', 'la tache a bien été créée');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, tache $tache)
{
$data= $request->validate(
[
'titre' => 'required|max:100',
'detail'=> 'required|max: 500',
]
);
$tache->titre = $request->titre;
$tache->detail = $request->detail;
$tache->save();
return redirect()->route('taches.index')->with('message', 'la tache a bien été créée');
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
mon formulaire :
@extends('template')
@section('content')
Modification d'une tache
id) }}" method="POST">
@csrf
@method('put')
Titre
titre) }}" placeholder="Titre de la tache">
@error('title')
{{ $message }}
@enderror
Detail
{{ old('detal', $tache->detail) }}
@error('detail')
{{ $message }}
@enderror
Valider
@endsection
bestmomo
Bonjour,
Je pense que le souci vient du fait qu’il y a un paramètre « tach » mais dans le contrôleur c’est nommé « tache ». Il faudrait harmoniser ça.
delacoche
bonjour , merci , mais svp à quel ligne vous voyez l’erreur?
delacoche
bonjours , apres la l’enregistrement dans mon formulaire d’un nouveau film on me retourne une erreur :
403
THIS ACTION IS UNAUTHORIZED.
merci pour votre aide
bestmomo
Bonjour,
Il faudrait vérifier dans la classe FormRequest qu’on retourne bien true dans la méthode authorize.
delacoche
bonjour , svp j’ai vraiment du mal à terminer le tutoriel , j’ai corrigé la dans la classe formrequest et j’ai ramené à true , mais le formulaire ne me renvoie rien et reste sur la meme page , de plus , la base de donnée n’est pas mise à jour.
bestmomo
Bonjour,
Les causes peuvent être multiples. Tu devrais récupérer le ZIP pour avoir le projet fonctionnel.
Miguel
merci vous avez resolu mon pb