Logomark

LARAVEL

Un framework qui rend heureux
Voir cette catégorie
Vers le bas
Voir cette série
Cours Laravel 12 – les données – les ressources (2/2)
Jeudi 6 mars 2025 00:12

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.

Pour vous simplifier la vie, voilà le code de démarrage de cet article qui tient donc compte de tout ce qu’on a vu dans le précédent.

Créer un film

Les routes

Pour la création d'un film, il nous faut deux routes :

  • une pour afficher le formulaire de création
  • une seconde 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\FilmRequest;

...

public function create(): View
{
    return view('create');
}

public function store(FilmRequest $filmRequest): RedirectResponse
{
    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 is-fullwidth">
                    <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 :

  • une pour afficher le formulaire de modification
  • une seconde 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): View
{
    return view('edit', compact('film'));
}

public function update(FilmRequest $filmRequest, Film $film): RedirectResponse
{
    $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

Dans le précédent article, on a ordonné les films à partir de leur titre avec orderBy. Il existe une syntaxe plus compacte pour aboutir au même résultat :

public function index(): View
{
    $films = Film::oldest('title')->paginate(5);

On a toujours les films dans l'ordre désiré :

Finalement aussi, quel intérêt de faire apparaître l'identifiant ? Surtout maintenant qu'ils sont 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, ceux-ci 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(): void
{
    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 supprimera un film, on le conservera dans la base et on renseignera la colonne deleted_at

Les routes

On ajoute deux routes pour les deux nouvelles actions :

Route::controller(FilmController::class)->group(function () {
    Route::delete('films/force/{film}', 'forceDestroy')->name('films.force.destroy');
    Route::put('films/restore/{film}', '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(): View
{
    $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 :

/**
 * Remove the specified resource from storage.
 */
public function forceDestroy($id): RedirectResponse
{
    Film::withTrashed()->whereId($id)->firstOrFail()->forceDelete();

    return back()->with('info', 'Le film a bien été supprimé définitivement dans la base de données.');
}

/**
 * Restore the specified resource from storage.
 */
public function restore($id): RedirectResponse
{
    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->trashed()) class="has-background-grey-lighter" @endif>
        <td><strong>{{ $film->title }}</strong></td>
            <td>
                @if($film->trashed())
                    <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->trashed())
                @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. Voici en téléchargement le code final de cet article.

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 la suppression des enregistrements.


Par bestmomo

Aucun commentaire

Article précédent : Cours Laravel 12 – les données – les ressources (1/2)
Article suivant : Cours Laravel 12 – les données – la relation 1:n
Cet article contient un quiz. Vous devez être authentifié pour y avoir acces.