Laravel 11

Albums – Les albums 1/2

Dans ce chapitre, nous allons poursuivre la création de notre application de galerie photos en nous concentrant sur la fonctionnalité d’albums. Nous avons déjà vu comment organiser les photos de la galerie avec des catégories, lesquelles ne peuvent être créées, modifiées ou supprimées que par un administrateur.

Cette fois, nous allons permettre à chaque utilisateur inscrit d’organiser ses photos dans des albums personnels. Bien que certaines fonctionnalités soient similaires à celles des catégories, nous devrons ajouter deux nouvelles tables à la base de données et établir des relations entre elles. De plus, nous compléterons le menu de la barre de navigation, créerons un formulaire, les routes, pour gérer les albums.

À la fin de ce chapitre, les utilisateurs de notre application de galerie photos pourront créer, modifier et supprimer leurs propres albums, offrant ainsi une meilleure organisation et une expérience utilisateur améliorée.

Comme le sujet est assez copieux je vais diviser son traitement en deux articles.

La base de données

Dans ce chapitre, nous allons compléter notre base de données en ajoutant des tables pour gérer les albums. Nous aurons besoin d’une table pour stocker les informations relatives aux albums, telles que le nom, le slug et les dates. De plus, comme un album sera associé à un utilisateur, nous devrons établir une relation de type « One To Many » entre la table des albums et celle des utilisateurs (users).

Il sera également nécessaire de créer une relation entre les albums et les images. Étant donné qu’un album peut contenir plusieurs images et qu’une image peut appartenir à plusieurs albums, nous aurons besoin d’une relation de type « Many To Many » entre les tables des albums et des images.

Pour résumer, les principales étapes à suivre pour intégrer les albums dans notre application sont les suivantes:

  1. création d’une table pour les albums et définition des champs requis (nom, slug, dates, etc.)
  2. établissement d’une relation de type « One To Many » entre la table des users et celle des albums
  3. création d’une table de liaison (table pivot) pour la relation « Many To Many » entre les tables des albums et des images
  4. mise à jour du schéma de la base de données et des modèles Eloquent correspondants pour prendre en compte les nouvelles relations

Une fois ces étapes accomplies, nous disposerons d’une structure de base de données robuste et bien organisée, permettant de gérer efficacement les albums et leurs associations avec les utilisateurs et les images.

Les migrations

On va ajouter une migration pour la table albums (on en profite pour créer également le modèle) :

php artisan make:model Album --migration

Changez ainsi le code :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('albums', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug');
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('albums');
    }
};

On ajoute aussi une migration pour la table pivot entre les albums et les images :

php artisan make:migration create_album_image_table

Avec ce code :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('album_image', function (Blueprint $table) {
            $table->id();
            $table->foreignId('album_id')->constrained()->onDelete('cascade');
            $table->foreignId('image_id')->constrained()->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('album_image');
    }
};

On peut maintenant rafraîchir la base :

php artisan migrate:fresh --seed

On se retrouve avec cette organisation :

Les modèles

Dans le modèle Image on ajoute la relation :

use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Image extends Model
{
    ...

    public function albums(): BelongsToMany
    {
        return $this->belongsToMany (Album::class);
    }

Dans le modèle User on ajoute la relation :

public function albums()
{
    return $this->hasMany (Album::class);
}

On a créé un modèle Album en même temps que la migration :

On change le code :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Album extends Model
{
    use HasFactory;

    protected $fillable = [
        'name', 'slug',
    ];

    public function images(): belongsToMany
    {
        return $this->belongsToMany (Image::class);
    }

    public function user(): belongsTo
    {
        return $this->belongsTo (User::class);
    }
}

Création d’un album

La première action à prévoir est celle de la création d’un album. On va avoir besoin d’un formulaire pour entrer le nom de notre nouvel album. Le slug sera généré automatiquement à partir du nom.

On commence par créer un composant Volt pour cette création :

php artisan make:volt albums/create --class

Et on prévoit ce code :

<?php

use Livewire\Volt\Component;
use Illuminate\Validation\Rule;
use Mary\Traits\Toast;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;

new class extends Component 
{
    use Toast;
    
    public string $name = '';

    public function save(): void
    {
        $data = $this->validate([
            'name' => [
                'required',
                'string',
                'max:255',
                Rule::unique('albums')->where(function ($query) {
                    return $query->where('user_id', Auth::id());
                })
            ],
        ]);
    
        $data['slug'] = Str::of($this->name)->slug('-');
        Auth::user()->albums()->create($data);
    
        $this->success(__('Album created with success.'), redirectTo: '/albums');
    }
}; ?>

<div>
    <x-card class="h-screen flex items-center justify-center" title="{{__('Create a new album')}}"> 
        <x-form wire:submit="save"> 
            <x-input label="{{__('Name')}}" name="Name" wire:model="name" />     
            <x-slot:actions>
                <x-button label="{{__('Cancel')}}" link="/" />
                <x-button label="{{__('Save')}}" icon="o-paper-airplane" spinner="save" type="submit" class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Une petite remarque concernant la validation. Dans la table, le nom de l’album ne peut pas être unique puisque différents utilisateurs peuvent choisir un même nom pour un album. Par contre pour chaque utilisateur le nom doit être unique.

On ajoute la route :

Route::middleware('auth')->group(function () {
    ...
    Volt::route('albums/create', 'albums.create')->name('albums.create');
});

Des traductions :

"Create a new album": "Créer un nouvel album",
"Album created with success.": "L'album a bien été créé",
"Add album": "Ajouter un album"

Maintenant avec l’url …/abums/create pour un utilisateur connecté, on a le formulaire de création :

On vérifie la validation :

Et que l’album se crée bien dans la base :

Il ne nous manque plus qu’un lien dans le menu :

@auth  
    <x-menu-sub title="{{__('Images')}}" icon="o-photo">
        ...
        <x-menu-item title="{{__('Add album')}}" icon="o-plus" link="{{ route('albums.create') }}" />
    </x-menu-sub>
@endauth

Gérer les albums

Maintenant qu’on sait créer des albums, on va prévoir le code pour les gérer et dans un premier temps en afficher la liste et permettre une suppression. On crée un nouveau composant :

php artisan make:volt albums/index --class

Et le code :

<?php

use Livewire\Volt\Component;
use App\Models\Album;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;

new class extends Component 
{
    public function headers(): array
    {
        return [
            ['key' => 'name', 'label' => __('Name')],
            ['key' => 'slug', 'label' => 'Slug'],
        ];
    }

    public function delete($id): void
    {
        Album::destroy($id);
    }

    public function albums(): Collection
    {
        return Auth::user()->albums()->get();
    }

    public function with(): array
    {
        return [
            'albums' => $this->albums(),
            'headers' => $this->headers()
        ];
    }
}; ?>

<div>
    <x-header title="{{__('Albums')}}" separator progress-indicator />

    <x-card>
        <x-table :headers="$headers" :rows="$albums" link="albums/{id}/edit">

            @scope('actions', $album)
                <x-button icon="o-trash" wire:click="delete({{ $album->id }})" wire:confirm="{{__('Are you sure to delete this album?')}}" spinner class="btn-sm" />
            @endscope

        </x-table>
    </x-card>
</div>

Et une traduction :

"Are you sure to delete this album?": "Voulez-vous vraiment supprimer cet album ?"

Et enfin la route :

Route::middleware('auth')->group(function () {
    ...
    Volt::route('albums', 'albums.index')->name('albums.index');
    Volt::route('albums/create', 'albums.create')->name('albums.create');    
});

Avec l’URL …/albums/albums, on a maintenant la liste des albums :

Avec le bouton, on peut aussi supprimer un album.

Il ne nous manque plus que le lien dans le menu :

@auth  
    <x-menu-sub title="{{__('Images')}}" icon="o-photo">
        <x-menu-item title="{{__('Add image')}}" icon="o-plus" link="{{ route('images.create') }}" />
        <x-menu-item title="{{__('Manage albums')}}" icon="o-archive-box" link="{{ route('albums.index') }}" />
        <x-menu-item title="{{__('Add album')}}" icon="o-plus" link="{{ route('albums.create') }}" />
    </x-menu-sub>
@endauth

Une traduction :

"Manage albums": "Gestion des albums"

Et c’est bon pour le menu :

Modifier un album

La dernière action à prévoir est la modification d’un album, en fait la modification de son nom. On crée un nouveau composant :

php artisan make:volt albums/edit --class

On prévoit ce code :

<?php

use Livewire\Volt\Component;
use Mary\Traits\Toast;
use App\Models\Album;
use Illuminate\Validation\Rule;
use Illuminate\Support\Str;

new class extends Component 
{
    use Toast;
    
    public Album $album;
    public string $name = '';

    public function mount(): void
    {
        $this->fill($this->album);
    }

    public function save(): void
    {
        $data = $this->validate([
            'name' => [
                'required',
                'string',
                'max:255',
                Rule::unique('albums')->ignore($this->album->id)->where(function ($query) {
                    return $query->where('user_id', Auth::id());
                }),
            ],
        ]);

        $data['slug'] = Str::of($this->name)->slug('-');    
        $this->album->update($data);
    
        $this->success(__('Album updated with success.'), redirectTo: '/albums');
    }
}; ?>

<div>
    <x-card class="h-screen flex items-center justify-center" title="{{__('Update')}} {{ $album->name }}">
        <x-form wire:submit="save"> 
            <x-input label="{{__('Name')}}" name="Name" wire:model="name" />  
            <x-slot:actions>
                <x-button label="{{__('Cancel')}}" link="/albums" />
                <x-button label="{{__('Save')}}" icon="o-paper-airplane" spinner="save" type="submit" class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Ici encore la validation est un peu délicate. On a la même réflexion que pour la création d’un album. Mais en plus il faut ignorer l’album en cours de modification en cas de sauvegarde sans changement.

On ajoute la route :

Route::middleware('auth')->group(function () {
    ...
    Volt::route('albums/{album}/edit', 'albums.edit')->name('albums.edit');
});

Et une traduction:

"Album updated with success.": "L'album a bien été modifié"

On obtient le formulaire de modification de l’album quand on clique sur lui dans la liste de gestion :

Vous pouvez vérifier que tout fonctionne correctement.

Conclusion

Dans cet article on a commencé à s’occuper des albums. On a :

  • créer deux nouvelles tables dans la base
  • ajouté les relations
  • créé un formulaire de création des albums e son traitement
  • créé un tableau pour gérer les albums
  • créé un formulaire de modification des albums et son traitement
  • synchronisé le menu pour accéder à ces nouveaux éléments

Dans la prochaine étape on finira de s’occuper des albums en prévoyant d’affecter les photos aux albums et en affichant les photos par album.

 

 

Print Friendly, PDF & Email

Laisser un commentaire