Ce tutoriel est le troisième chapitre de notre série concernant la découverte de FilamentPHP :
- #1 Installation & listing
- #2 Création et édition
- #3 Les relations
Dans cet article, nous allons ajouter une notion de rôle à nos utilisateurs : un utilisateur pourra posséder un ou plusieurs rôles à travers une relation.
Voyons aujourd'hui ce que propose Filament afin de manipuler des relations entre nos différentes ressources !
Prérequis
Qui dit relation dit nouvelle table, commençons par créer un nouveau modèle Role
ainsi que sa migration associée à l'aide de la commande suivante :
1php artisan make:model Role --migration
La relation rôles sera de type belongsToMany
, nous vous conseillons notre tutoriel si vous voulez en savoir plus sur le fonctionnement et les possibilités offertes par ce type de relation.
Un rôle est donc lié à des utilisateurs :
1 2namespace App\Models; 3 4use App\Models\User; 5use Illuminate\Database\Eloquent\Model; 6use Illuminate\Database\Eloquent\Factories\HasFactory; 7 8class Role extends Model 9{10 use HasFactory;11 12 public function users() 13 { 14 return $this->belongsToMany(User::class); 15 } 16}
Modifions également notre modèle User
pour y ajouter la relation inverse, un utilisateur possède des rôles :
1<?php 2 3namespace App\Models; 4 5use App\Models\Role; 6use Laravel\Sanctum\HasApiTokens; 7use Illuminate\Notifications\Notifiable; 8use Illuminate\Database\Eloquent\Factories\HasFactory; 9use Illuminate\Foundation\Auth\User as Authenticatable;10 11class User extends Authenticatable12{13 use HasApiTokens, HasFactory, Notifiable;14 15 // [...]16 17 public function roles() 18 { 19 return $this->belongsToMany(Role::class); 20 } 21}
Il sera ensuite indispensable de modifier la migration, précédemment créée à l'aide de la commande artisan, afin d'y ajouter une table intermédiaire role_user
faisant le lien entre des rôles et des utilisateurs, ainsi qu'une propriété name
sur la table roles
:
1use Illuminate\Database\Migrations\Migration; 2use Illuminate\Database\Schema\Blueprint; 3use Illuminate\Support\Facades\Schema; 4 5return new class extends Migration 6{ 7 /** 8 * Run the migrations. 9 */10 public function up(): void11 {12 Schema::create('roles', function (Blueprint $table) {13 $table->id();14 $table->string('name'); 15 $table->timestamps();16 });17 18 Schema::create('role_user', function (Blueprint $table) { 19 $table->id(); 20 $table->unsignedBigInteger('user_id'); 21 $table->unsignedBigInteger('role_id'); 22 $table->timestamps(); 23 }); 24 }25 26 /**27 * Reverse the migrations.28 */29 public function down(): void30 {31 Schema::dropIfExists('roles');32 Schema::dropIfExists('role_user'); 33 }34};
Pour finir, utilisons le DatabaseSeeder
afin d'insérer des rôles en base de données, nous n'utiliserons pas de factory en raison de la simplicité de notre modèle :
1 2namespace Database\Seeders; 3 4use Illuminate\Database\Seeder; 5use Illuminate\Support\Facades\DB; 6 7class DatabaseSeeder extends Seeder 8{ 9 /**10 * Seed the application's database.11 */12 public function run(): void13 {14 DB::table('roles')->insert([ 15 ['name' => 'admin'], 16 ['name' => 'member'], 17 ['name' => 'contributor'], 18 ]); 19 }20}
Exécutez maintenant les commandes artisan migrate
et db:seed
et notre application sera prête à manipuler cette nouvelle relation !
Créer la ressource Rôle
Le rôle étant une ressource comme les autres, nous devons la créer dans notre projet à l'aide de la commande suivante :
1php artisan make:filament-resource Role
Nous n'allons pas détailler ici la création des différentes page constituant la ressource "Role", cette dernière sera, on ne peut plus classique, possédant un unique champ "name".
Vous trouverez dans les deux premiers chapitres de cette série de tutoriels toutes les informations nécessaires afin d'ajouter cette nouvelle ressource à votre application.
Le code de la ressource Rôle est disponible à cette URL.
Afficher le rôle d'un utilisateur
Afficher la valeur d'une relation se fera depuis le composant TextColumn à l'aide d'une simple notation à point :
1public static function table(Table $table): Table 2{ 3 return $table 4 ->columns([ 5 TextColumn::make('name') 6 ->searchable(), 7 TextColumn::make('roles.name'), 8 TextColumn::make('email'), 9 // [...]10 ])11 // [...]12}
Attardons-nous sur la notation roles.name
: le mot roles
fait référence à la relation belongsToMany
présente dans le modèle User
, tandis que name
correspond à la propriété du modèle Role
que nous souhaitons afficher.
Qu'importe le type de relation, vous n'aurez aucune préoccupation supplémentaire à avoir, notamment concernant des contraintes de edge loading, Filament se chargera de tout à votre place !
Il sera également possible d'utiliser des fonctions d'agrégation, telles que le count, pour déterminer le nombre de rôles de chaque utilisateur, toujours à l'aide du composant TextColumn
:
1public static function table(Table $table): Table 2{ 3 return $table 4 ->columns([ 5 TextColumn::make('name') 6 ->searchable(), 7 TextColumn::make('roles.name'), 8 TextColumn::make('roles_count') 9 ->counts('roles') 10 ->sortable() 11 ->label('NB roles'), 12 TextColumn::make('email'),13 // [...]14 ])15 // [...]16}
Attention, veuillez ici à bien nommer votre TextColumn
, sa valeur devra impérativement correspondre au nom de la relation suffixé par _count
, dans le cas contraire, Filament lèvera une erreur.
Pour finir, ajoutons un simple filtre permettant de rapidement trier nos utilisateurs à l'aide du composant SelectFilter :
1public static function table(Table $table): Table 2{ 3 return $table 4 ->columns([ 5 // [...] 6 ]) 7 ->defaultSort('created_at', 'desc') 8 ->filters([ 9 SelectFilter::make('roles') 10 ->relationship('roles', 'name'), 11 ])12 ->actions([13 // [...]14 ])15 ->bulkActions([16 // [...]17 ]);18}
Quelques options supplémentaires sur votre SelectFilter
le rendrons plus réactif en ajoutant, par exemple, la possibilité de rechercher dans les rôles présents ou bien encore en chargeant en amont les différentes relations en mémoire à l'affichage de la page.
1SelectFilter::make('roles')2 ->relationship('roles', 'name')3 ->badge() 4 ->searchable() 5 ->preload(),
Notre listing est désormais opérationel, nous affichons les rôles de chaque utilisateur et avons la possibilité de les filtrer.
Il est désormais temps d'ajouter cette notion de rôle dans les formulaires d'édition et de création d'un utilisateur !
Modifier le rôle d'un utilisateur
L'ajout de la gestion des rôles dans les pages d'édition et de création d'un utilisateur sera tout aussi simple.
Commençons par le formulaire de création, rappelez-vous, nous avions ajouté un wizard dans notre précédent chapitre, ce dernier se trouve dans le fichier CreateUser
.
Rappel : un Wizard est un formulaire en multi étapes
1public function getSteps(): array 2{ 3 return [ 4 Step::make('Information') 5 ->schema([ 6 // [...] 7 ]), 8 Step::make('Password') 9 ->schema([10 // [...]11 ]),12 Step::make('Validation')13 ->schema([14 Select::make('roles') 15 ->relationship('roles', 'name'), 16 Toggle::make('auto_verified')17 ->label('Validate user email')18 ->default(false),19 ]),20 ];21}
Un composant Select sera amplement suffisant afin de proposer les différents rôles disponibles depuis le wizard de création des utilisateurs.
Concernant la méthode relationship
, le premier argument sera le nom de la relation (roles) tandis que le second sera le nom de la propriété du modèle Role
à afficher (name).
Ajoutons également la possibilité de sélectionner plusieurs rôles à l'aide de la méthode multiple
ainsi qu'un pré-chargement des rôles similaire à celui présent sur notre page de listing.
1Select::make('roles')2 ->relationship('roles', 'name')3 ->multiple() 4 ->preload(),
À l'instar des autres champs, celui-ci peut être rendu obligatoire !
Le formulaire de création est désormais fonctionnel et vous permettra de sélectionner un ou plusieurs rôle à votre utilisateur !
Concernant l'édition, il suffira d'insérer quelques lignes similaires dans la méthode form
de notre UserResource
et le tour sera joué :
1public static function form(Form $form): Form 2{ 3 return $form 4 ->schema([ 5 Section::make() 6 ->schema([ 7 TextInput::make('name') 8 ->required() 9 ->maxLength(10),10 TextInput::make('email')11 ->disabled(),12 Select::make('roles') 13 ->relationship('roles', 'name') 14 ->multiple() 15 ->preload(), 16 DatePicker::make('email_verified_at')17 ->maxDate(now()),18 ])19 ]);20}
Relation manager
Nos modèles rôles sont liés à des utilisateurs grâce à la relation belongsToMany
que nous avons précédemment créée : un rôle possède des utilisateurs et inversement.
Il se peut que nous soyons un jour amenés à vouloir afficher tous les utilisateurs d'un rôle en particulier, pour répondre à ce besoin, Filament met à disposition les relations manager.
Un relation manager nous permettra d'afficher tous les relations associés à un modèle spécifique depuis la page d'édition de ce dernier.
Pour commencer, créons un relation manager à l'aide de la commande suivante :
1php artisan make:filament-relation-manager UserResource users name
-
UserResource
est le nom de la ressource que nous voulons encapsuler. -
users
est le nom de la méthode qui nous permettra de récupérer les relations. -
name
est la propriété que nous allons afficher, le nom étant celui des utilisateurs.
L'exécution de la commande précédente devrait vous amener à créer une nouvelle classe UsersRelationManager
, cette dernière devra être référencée dans la méthode getRelations
de notre RoleResource
pour être prise en compte :
1public static function getRelations(): array2{3 return [4 UsersRelationManager::class, 5 ];6}
Notre relation manager est désormais pratiquement prêt, cependant, il lui manque quelques configurations afin d'être pleinement opérationnel.
Un RelationManager
fraîchement créé possède, par défaut, deux méthodes : form et table :
1class UsersRelationManager extends RelationManager 2{ 3 protected static string $relationship = 'users'; 4 5 public function form(Form $form): Form 6 { 7 return $form 8 ->schema([ 9 //10 ]);11 }12 13 public function table(Table $table): Table14 {15 return $table16 ->recordTitleAttribute('name')17 ->columns([18 //19 ])20 ->filters([21 //22 ])23 ->headerActions([24 //25 ])26 ->actions([27 Tables\Actions\EditAction::make(),28 Tables\Actions\DeleteAction::make(),29 ])30 ->bulkActions([31 Tables\Actions\BulkActionGroup::make([32 Tables\Actions\DeleteBulkAction::make(),33 ]),34 ]);35 }36}
Dans sa structure, un RelationManager est très proche d'une resource
La méthode table
configure l'affichage de notre utilisateur à travers le relation manager, dans notre cas, nous souhaitons uniquement afficher son nom :
1public function table(Table $table): Table2{3 return $table4 ->recordTitleAttribute('name')5 ->columns([6 Tables\Columns\TextColumn::make('name'), 7 ])8 // [...]9}
La méthode form
, quant à elle, permet d'ajouter un formulaire d'édition d'un utilisateur. Ce formulaire sera en doublon de celui précédemment créé dans notre UserResource
... cela signifie que nous pourrions modifier un utilisateur directement depuis l'affichage d'un rôle !
Ce comportement peut s'avérer perturbant et nous ne l'utiliserons pas dans ce tutoriel.
Notre relation manager est désormais complet et vous verrez désormais apparaître tous les utilisateurs associés à un rôle lorsque vous vous rendrez sur la page d'édition du rôle :
Dans ce troisième tutoriel, nous avons ajouté des relations entre nos modèles et mis en place notre première relation manager, dans le prochain chapitre, nous aborderons des tips et des conseils sur l'utilisation de FilamentPHP !
Source : https://github.com/laravel-fr/support-filamentphp
A lire
Autres articles de la même catégorie
Le driver email failover
Protégez-vous des indisponibilités de votre serveur e-mails grace à un driver failover !
Inversion de dépendance en Laravel
Améliorons l'architecture de nos projets en implémentant une dépendance externe à travers 3 approches différentes.
Traquer un utilisateur dans les logs
Laravel offre la possibilité d'ajouter un contexte unique à chaque ligne de log, voyons comment utiliser cette feature pour traquer les erreurs d'un utilisateur !