Laravel dispose d'une relation belongsToMany
permettant la définition d'une relation Many to Many(n-n) entre deux modèles.
L’exemple le plus courant et probablement le plus simple à apprehender est celui d’une gestion de rôles : “un utilisateur peut disposer d'un à plusieurs rôles et un rôle peut être attaché à un ou plusieurs utilisateurs”.
Afin de manipuler cette relation le framework propose une méthode attach
facilitant l’utilisation du belongsToMany
, voyons plus précisément ce qu'elle permet !
Prérequis
Avant de poursuivre la suite de ce tutoriel, nous allons créer les 3 tables suivantes :
1Schema::create('users', function (Blueprint $table) { 2 $table->id(); 3 $table->timestamps(); 4}); 5 6Schema::create('roles', function (Blueprint $table) { 7 $table->id(); 8 $table->string('name'); 9 $table->timestamps();10});11 12Schema::create('role_user', function (Blueprint $table) {13 $table->id();14 $table->unsignedBigInteger('user_id');15 $table->unsignedBigInteger('role_id');16 $table->timestamps();17});
La table role_user
qu'on appellera pour la suite de ce tuto "pivot" servira de table intermédiaire permettant de lier les utilisateurs à des roles.
Ajoutons également les models User
et Role
ayant chacun une relation belongsToMany
et leur factories respectives :
1class User extends Model 2{ 3 use HasFactory; 4 5 public function roles() 6 { 7 return $this->belongsToMany(Role::class); 8 } 9}10 11class Role extends Model12{13 use HasFactory;14 15 public function users()16 {17 return $this->belongsToMany(User::class);18 }19}
La commande make
peut vous simplifier la création des modèles et de leurs factories associées. : php artisan make:model Role --factory
Pour le moment la table role_user
ne possède pas de Model, nous reviendrons sur ce détail plus tard dans ce tutoriel.
Les bases de la méthode attach
Apres avoir créé des instances de User
et de Role
, utilisons la méthode attach
pour assigner un role "admin" à notre utilisateur :
1$user = User::factory()->create();2 3$role = Role::factory()->create([4 'name' => 'admin',5]);6 7$user->roles()->attach($role);
Une ligne apparaît désormais dans notre table pivot role_user
liant les deux models comme dans notre schema suivant :
Nous pouvons des lors facilement récupérer les roles d'un utilisateur depuis notre relation roles()
du model User
:
1$user = User::first(); 2 3$roles = $user->roles()->get(); 4 5dd($roles->toArray()); 6 7# array:4 [ 8# "id" => 1 9# "name" => "admin",10# "created_at" => "2023-02-20T18:57:47.000000Z"11# "updated_at" => "2023-02-20T18:57:47.000000Z"12# "pivot" => array:4 [13# "user_id" => 114# "role_id" => 115# ]16# ]
On retrouve ainsi dans le dump les différents roles de notre utilisateur, dans le cas présent le role Admin !
Pour la suite de ce tutoriel, nous utiliserons la méthode with pour accéder plus rapidement à la relation roles
!
Attacher plusieurs éléments
Nous venons d’utiliser notre premiere relation belongsToMany
en assignant un role à un utilisateur, afin de faciliter une création en masse, il est envisageable d’envoyer une liste de role à associer à la méthode attach
:
1$user = User::factory()->create();2 3$roles = Role::factory()->count(3)->create();4 5$user->roles()->attach($roles);
La méthode peut recevoir une instance de model, une collection, un array …
Désormais, nos 3 models roles bien sont liés au model User, nous pouvons vérifier les ids des roles associés avec un pluck !
1$user = User::with('roles')->first();2 3$user->roles->pluck('id'); // 1, 2, 3
Attention, gardez en tête que la méthode attach
ne vous protège pas des doublons !
Dans la situation suivante, la même instance de role sera associé 3 fois à l’utilisateur.
1$user = User::factory()->create(); 2 3$role = Role::factory()->create(); 4 5$user->roles()->attach($role); 6$user->roles()->attach($role); 7$user->roles()->attach($role); 8 9$user = User::with('roles')->first();10 11$user->roles->pluck('id'); // 1, 1, 1
Attacher avec des attributs
Le pivot étant une table intermédiaire entre users
et roles
elle peut également contenir des informations propre à la relation !
Imaginons par exemple que nous souhaitons ajouter une notion d'activation à la relation, pour ce faire, ajoutons un nouveau champ "active" à notre migration role_user
:
1Schema::create('role_user', function (Blueprint $table) {2 $table->id();3 $table->unsignedBigInteger('user_id');4 $table->unsignedBigInteger('role_id');5 $table->integer('active')->default(0);6 $table->timestamps();7});
La commande
php artisan migrate:fresh
vous aidera à remettre la bdd à zero !
Bien qu’une table pivot soit en partie gérée par le framework, vous êtes totalement libre d’y ajouter autant de colonne que vous le souhaitez.
Lors de l’utilisation de la méthode attach
, un second argument attributes
est disponible afin de transmettre des valeurs additionnelles à sauvegarder dans le pivot, dans notre cas nous souhaitons ajouter une valeur active
:
1$user = User::factory()->create();2 3$role = Role::factory()->create();4 5$user->roles()->attach($role, attributes: [6 'active' => 0,7]);
Ensuite, vous devez renseigner l'existence de cette valeur directement dans la relation du model User
grace à la méthode withPivot
:
1class User extends Model2{3 public function roles()4 {5 return $this->belongsToMany(Role::class)->withPivot('active');6 }7}
Il est possible d’envoyer un array ou plusieurs arguments successifs à withPivot.
Désormais, quand vous récupérerez les roles d'un utilisateur, la valeur du champ active
sera directement accessible.
1$user = User::with('roles')->first();2 3$role = $user->roles->first();4 5$role->pivot->active; // 0
Récupérer les timestamps d’un pivot
Notre table pivot possède les timestamps created_at
et updated_at
correspondant respectivement à la date de création du pivot et à sa dernière modification.
Par défaut, ces valeurs ne sont pas présentes lors de la récupération des pivots depuis une relation belongsToMany
pour les obtenir il est nécessaire de modifier la relation roles et d'y ajouter la méthode withTimestamps
.
1class User extends Model2{3 public function roles()4 {5 return $this->belongsToMany(Role::class)->withTimestamps();6 }7}
Désormais, le pivot possède ses timestamps !
1$user = User::with('roles')->first(); 2 3$role = $user->roles->first(); 4 5dd($role->pivot->toArray()); 6 7# array:4 [ 8# "user_id" => 11 9# "role_id" => 2210# "created_at" => "2023-02-20T18:44:33.000000Z"11# "updated_at" => "2023-02-20T18:44:33.000000Z"12# ]
Ajouter un model au pivot
Jusqu'à présent, le table role_user
n'était pas matérialisée par un model dans notre application, si vous le souhaitez, il est possible d'en ajouter un afin de profiter de quelques fonctionnalités supplémentaires.
1namespace App\Models;2 3use Illuminate\Database\Eloquent\Relations\Pivot;4 5class RoleUser extends Pivot6{7 //8}
La class Pivot
hérite de la class Model
, cela signifie que RoleUser
bénéficie de toutes les fonctionnalités de Eloquent ! Cette class peut donc profiter des casts, des relations, des events ...
Dans notre exemple, essayons de cast le champ active
en boolean
:
1class RoleUser extends Pivot2{3 protected function casts(): array4 {5 return [6 'active' => 'boolean',7 ];8 }9}
Pour finir, vous n'avez plus qu'à renseigner l'utilisation de cette class pivot dans la relation et le tour est joué !
1class User extends Model2{3 public function roles()4 {5 return $this->belongsToMany(Role::class)6 ->withPivot('active')7 ->using(RoleUser::class);8 }9}
Il reste nécessaire de renseigner le
withPivot
pour charger la propriété "active"
Nous récupérons dorénavant les roles depuis la class pivot RoleUser
et profitons du cast en boolean !
1$user = User::factory()->create(); 2 3$role = Role::factory()->create(); 4 5$user->roles()->attach($role, attributes: [ 6 'active' => 1, 7]); 8 9$user->refresh();10 11$role = $user->roles->first();12 13$role->pivot->active; // true
Toggle
Le toggle est une méthode permettant de basculer les relations d'un belongsToMany
.
Si la méthode toggle
reçoit une instance de Role
qui n'est pas présente dans la table role_user
, elle sera alors ajoutée.
Dans le cas contraire, si la relation est d'hors et deja présente, cette dernière sera supprimée.
1$user = User::factory()->create(); 2$role = Role::factory()->create(); 3 4$user->roles()->toggle($role); 5 6$user->roles->pluck('id'); // 1 7 8$user->roles()->toggle($role); 9 10$user->refresh();11 12$user->roles->pluck('id'); // null
L'utilisation du refresh est nécessaire pour rafraichir les relations de
$user
A lire
Autres articles de la même catégorie
Comment fonctionne Laravel Sanctum ?
Arrêtez de débugger vos SPA à l'aveugle et maîtrisez enfin les rouages de ce système d'authentification.
Laravel Jutsu
Les Seeders
Créez rapidement des jeux de données grâce aux seeders !
Antoine Benevaut
Cast un attribut de modèle en DTO
Utilisons un DTO en tant qu’attribut afin de rendre notre code plus consistant et éviter les surprises !
William Suppo