Il y a souvent un moment dans la vie de tout développeur où vous devez interagir avec une base de données. C’est là qu’Eloquent, le mappeur objet-relationnel (Object-Relational Mapper ou ORM) de Laravel, rend le processus d’interaction avec les tables de votre base de données intuitif et naturel.
En tant que professionnel, il est essentiel que vous reconnaissiez et compreniez les six types de relations clés que nous allons passer en revue.
Que sont les relations dans Eloquent ?
Lorsque vous travaillez avec des tables dans une base de données relationnelle, nous pouvons caractériser les relations comme des connexions entre les tables. Cela vous aide à organiser et à structurer les données sans effort, ce qui permet une meilleure lisibilité et une meilleure manipulation des données. En pratique, il existe trois types de relations dans les bases de données :
- une à une – Un enregistrement dans une table est associé à une, et une seule, dans une autre table. Par exemple, une personne et un numéro de sécurité sociale.
- une-à-plusieurs – Un enregistrement est associé à plusieurs enregistrements dans une autre table. Par exemple, un écrivain et ses blogs.
- plusieurs à plusieurs – Plusieurs enregistrements d’une table sont associés à plusieurs enregistrements d’une autre table. Par exemple, les étudiants et les cours auxquels ils sont inscrits.
Laravel permet d’interagir et de gérer les relations entre les bases de données en utilisant la syntaxe orientée objet d’Eloquent.
En plus de ces définitions, Laravel introduit d’autres relations, à savoir :
- Has Many Through
- Relations polymorphes
- Plusieurs-à-plusieurs Polymorphes
Prenons l’exemple d’un magasin dont l’inventaire contient une variété d’articles, chacun dans sa propre catégorie. Il est donc logique, d’un point de vue commercial, de diviser la base de données en plusieurs tables. Cela pose des problèmes, car vous ne souhaitez pas interroger chaque table.
Nous pouvons facilement créer une simple relation un-à-plusieurs dans Laravel pour nous aider, par exemple lorsque nous avons besoin d’interroger les produits, nous pouvons le faire en utilisant le modèle Product.
Relation un-à-un
Première relation de base proposée par Laravel, elle associe deux tables de manière à ce qu’une ligne de la première table soit corrélée à une seule ligne de l’autre table.
Pour voir cela en action, nous devons créer deux modèles avec leur propre migration :
php artisan make:model Tenant
Php artisan make:model Rent
À ce stade, nous avons deux modèles, l’un étant le locataire et l’autre son loyer.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
/**
* Get the rent of a Tenant
*/
public function rent()
{
return $this->hasOne(Rent::class);
}
}
Comme Eloquent détermine la relation de clé étrangère en se basant sur le nom du modèle parent (Tenant dans ce cas), le modèle Rent suppose qu’il existe une clé étrangère tenant_id.
Nous pouvons facilement l’écraser en ajoutant un argument supplémentaire à la méthode hasOne:
return $this- >hasOne(Rent::class, "custom_key");
Eloquent suppose également qu’il existe une correspondance entre la clé étrangère définie et la clé primaire du parent (modèle Tenant). Par défaut, il cherchera à faire correspondre tenant_id avec la clé id de l’enregistrement Tenant. Nous pouvons remplacer cela par un troisième argument dans la méthode hasOne, de manière à ce qu’il corresponde à une autre clé :
return $this->hasOne(Rent::class, "custom_key", "other_key");
Maintenant que nous avons défini la relation biunivoque entre les modèles, nous pouvons l’utiliser facilement, comme ci-dessous :
$rent = Tenant::find(10)->rent;
Avec cette ligne de code, nous obtenons le loyer du locataire avec l’identifiant 10 s’il existe.
Relation d’un à plusieurs
Comme la relation précédente, celle-ci définira les relations entre un modèle monoparental et plusieurs modèles enfants. Il est peu probable que notre locataire n’ait qu’une seule facture de loyer, car il s’agit d’un paiement récurrent ; il aura donc plusieurs paiements.
Dans ce cas, notre relation précédente présente des défauts et nous pouvons les corriger :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
/**
* Get the rents of a Tenant
*/
public function rent()
{
return $this->hasMany(Rent::class);
}
}
Avant d’appeler la méthode pour obtenir les loyers, il est bon de savoir que les relations servent à construire des requêtes, de sorte que nous pouvons ajouter des contraintes (comme un loyer entre deux dates, un paiement minimum, etc :
$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();
Et comme pour la relation précédente, nous pouvons écraser les clés étrangères et locales en passant des arguments supplémentaires :
return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");
Nous disposons désormais de tous les loyers d’un locataire, mais que faire lorsque nous connaissons le loyer et que nous voulons savoir à qui il appartient ? Nous pouvons utiliser la propriété belongsTo :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rent extends Model
{
/**
* Return the tenant for the rent
*/
public function tenant()
{
return $this->belongsTo(Tenant::class);
}
}
Et maintenant, nous pouvons facilement trouver le locataire :
$tenant = Rent::find(1)->tenant;
Pour la méthode belongsTo, nous pouvons également écraser les clés étrangères et locales comme nous l’avons fait précédemment.
Relation de type un sur plusieurs (Has-One-Of-Many)
Étant donné que notre modèle Locataire peut être associé à de nombreux modèles Location, nous voulons facilement récupérer le modèle le plus récent ou le plus ancien des relations.
Une façon pratique de le faire est de combiner les méthodes hasOne et ofMany :
public function latestRent() {
return $this->hasOne(Rent::class)->latestOfMany();
}
public function oldestRent() {
return $this->hasOne(Rent::class)->oldestOfMany();
}
Par défaut, nous récupérons les données sur la base de la clé primaire, qui peut être triée, mais nous pouvons créer nos propres filtres pour la méthode ofMany :
return $this->hasOne(Rent::class)->ofMany('price', 'min');
Relations HasOneThrough et HasManyThrough
Les méthodes -Through suggèrent que nos modèles devront passer par un autre modèle pour établir une relation avec le modèle souhaité. Par exemple, nous pouvons associer le loyer au propriétaire, mais le loyer doit d’abord passer par le locataire pour atteindre le propriétaire.
Les clés des tables nécessaires à cet effet ressembleraient à ceci :
rent
id - integer
name - string
value - double
tenants
id - integer
name - string
rent_id - integer
landlord
id - integer
name - string
tenant_id - integer
Après avoir visualisé l’aspect de nos tables, nous pouvons créer les modèles :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rent extends Model
{
/**
* Return the rents' landlord
*/
public function rentLandlord()
{
return $this->hasOneThrough(Landlord::class, Tenant::class);
}
}
Le premier argument de la méthode hasOneThrough est le modèle auquel vous voulez accéder, et le deuxième argument est le modèle que vous allez traverser.
Et comme précédemment, vous pouvez écraser les clés étrangères et locales. Maintenant que nous avons deux modèles, nous en avons deux de chaque à écraser dans cet ordre :
public function rentLandlord()
{
return $this->hasOneThrough(
Landlord::class,
Tenant::class,
"rent_id", // Foreign key on the tenant table
"tenant_id", // Foreign key on the landlord table
"id", // Local key on the tenant class
"id" // Local key on the tenant table
);
}
De même, la relation « Has Many Through » de Laravel Eloquent est utile lorsque vous souhaitez accéder aux enregistrements d’une table distante par le biais d’une table intermédiaire. Prenons un exemple avec trois tables :
- country
- users
- games
Chaque pays a plusieurs utilisateurs et chaque utilisateur a plusieurs jeux. Nous voulons récupérer tous les jeux appartenant à un pays dans la table users
Vous devez définir les tables comme suit :
country
id - integer
name - string
user
id - integer
country_id - integer
name - string
games
id - integer
user_id - integer
title - string
Vous devez maintenant définir le modèle Eloquent pour chaque table :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
protected $fillable = ['name'];
public function users()
{
return $this->hasMany(User::class);
}
public function games()
{
return $this->hasManyThrough(Games::class, User::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = [article_id, 'name'];
public function country()
{
return $this->belongsTo(Country::class);
}
public function posts()
{
return $this->hasMany(Post::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Game extends Model
{
protected $fillable = ['user_id', 'title'];
public function user()
{
return $this->belongsTo(User::class);
}
}
Nous pouvons maintenant appeler la méthode games() du modèle Pays pour obtenir tous les jeux, car nous avons établi la relation « Has Many Through » entre Pays et Jeu par l’intermédiaire du modèle Utilisateur.
<?php
$country = Country::find(159);
// Retrieve all games for the country
$games = $country->games;
Relation de plusieurs à plusieurs
La relation de plusieurs à plusieurs est plus compliquée. Un bon exemple est celui d’un employé qui a plusieurs rôles. Un rôle peut également être attribué à plusieurs employés. C’est la base de la relation de plusieurs à plusieurs.
Pour cela, nous devons disposer des tables employees, roles et role_employees .
La structure des tables de notre base de données se présente comme suit :
employees
id - integer
name - string
roles
id - integer
name - string
role_employees
user_id - integer
role_id - integer
En connaissant la structure des tables de la relation, nous pouvons facilement définir notre modèle Employé comme appartenant à plusieurs modèles Rôle.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
public function roles()
{
return $this- >belongsToMany(Role::class);
}
}
Une fois cette définition effectuée, nous pouvons accéder à tous les rôles d’un employé et même les filtrer :
$employee = Employee::find(1);
$employee->roles->forEach(function($role) { // });
// OR
$employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();
Comme toutes les autres méthodes, nous pouvons écraser les clés étrangères et locales de la méthode belongsToMany.
Pour définir la relation inverse de la méthode belongsToMany, il suffit d’utiliser la même méthode, mais sur la méthode enfant, avec le parent comme argument.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function employees()
{
return $this->belongsToMany(Employee::class);
}
}
Utilisations de la table intermédiaire
Comme nous l’avons peut-être remarqué, lorsque nous utilisons la relation plusieurs-à-plusieurs, nous sommes toujours censés avoir une table intermédiaire. Dans ce cas, nous utilisons la table role_employees.
Par défaut, notre tableau croisé dynamique ne contient que les attributs id. Si nous voulons d’autres attributs, nous devons les spécifier comme suit :
return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");
Si nous voulons raccourcir le tableau croisé dynamique pour les horodatages, nous pouvons le faire :
return $this->belongsToMany(Employee::class)->withTimestamps();
Il faut savoir que nous pouvons personnaliser le nom « pivot » pour qu’il corresponde mieux à notre application :
return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");
Filtrer les résultats d’une requête éloquente est un must pour tout développeur qui souhaite améliorer son jeu et optimiser ses applications Laravel.
C’est pourquoi Laravel propose une fonctionnalité fantastique, les pivots, qui peuvent être utilisés pour filtrer les données que nous voulons collecter. Ainsi, au lieu d’utiliser d’autres fonctionnalités comme les transactions de base de données pour obtenir nos données en morceaux, nous pouvons les filtrer avec des méthodes utiles comme wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, wherePivotNotNull et nous pouvons les utiliser lors de la définition des relations entre les tables !
return $this->belongsToMany(Employee::class)->wherePivot('promoted', 1);
return $this->belongsToMany(Employee::class)->wherePivotIn('level', [1, 2]);
return $this->belongsToMany(Employee::class)->wherePivotNotIn('level', [2, 3]);
return $this->belongsToMany(Employee::class)->wherePivotBetween('posted_at', ['2023-01-01 00:00:00', '2023-01-02 00:00:00']);
return $this->belongsToMany(Employee::class)->wherePivotNull('expired_at');
return $this->belongsToMany(Employee::class)->wherePivotNotNull('posted_at');
Une dernière fonctionnalité étonnante est la possibilité de classer les pivots :
return $this->belongsToMany(Employee::class)
->where('promoted', true)
->orderByPivot('hired_at', 'desc');
Relations polymorphes
Le mot « polymorphe » vient du grec et signifie « plusieurs formes » Ainsi, un modèle dans notre application peut prendre plusieurs formes, ce qui signifie qu’il peut avoir plus d’une association. Imaginez que nous construisions une application avec des blogs, des vidéos, des sondages, etc. Un utilisateur peut créer un commentaire pour chacun de ces éléments. Un utilisateur peut créer un commentaire pour n’importe lequel de ces éléments. Par conséquent, un modèle Commentaire peut appartenir aux modèles Blogs, Vidéos et Sondages.
Polymorphe un à un
Ce type de relation est similaire à une relation univoque standard. La seule différence est que le modèle enfant peut appartenir à plusieurs types de modèles avec une seule association.
Prenez, par exemple, un modèle Locataire et Propriétaire, il peut partager une relation polymorphe avec un modèle Facture d’eau.
La structure de la table peut être la suivante :
tenants
id – integer
name – string
landlords
id – integer
name – string
waterbills
id – integer
amount – double
waterbillable_id
waterbillable_type
Nous utilisons waterbillable_id pour l’identifiant du propriétaire ou du locataire, tandis que waterbillable_type contient le nom de la classe du modèle parent. La colonne type est utilisée par Eloquent pour déterminer le modèle parent à retourner.
La définition du modèle pour une telle relation se présente comme suit :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WaterBill extends Model
{
public function billable()
{
return $this->morphTo();
}
}
class Tenant extends Model
{
public function waterBill()
{
return $this->morphOne(WaterBill::class, 'billable');
}
}
class Landlord extends Model
{
public function waterBill()
{
return $this->morphOne(WaterBill::class, 'billable');
}
}
Une fois que tout cela est en place, nous pouvons accéder aux données du modèle du propriétaire et du locataire :
<?php
$tenant = Tenant::find(1)->waterBill;
$landlord = Landlord::find(1)->waterBill;
Une relation polymorphe de type un à plusieurs
Il s’agit d’une relation similaire à une relation classique de type « un à plusieurs », la seule différence essentielle étant que le modèle enfant peut appartenir à plusieurs types de modèles, en utilisant une seule association.
Dans une application comme Facebook, les utilisateurs peuvent commenter des messages, des vidéos, des sondages, des événements en direct, etc. Avec une association polymorphe, nous pouvons utiliser une seule table de commentaires pour stocker les commentaires de toutes les catégories que nous avons. La structure de notre table ressemblerait à ceci :
posts
id – integer
title – string
body – text
videos
id – integer
title – string
url – string
polls
id – integer
title – string
comments
id – integer
body – text
commentable_id – integer
commentable_type – string
Le commentable_id étant l’identifiant de l’enregistrement, et le commentable_type étant le type de classe, de sorte qu’eloquent sache ce qu’il doit chercher. Quant à la structure du modèle, elle est très similaire à la structure polymorphe one-to-many :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
class Poll extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Live extends Model
{
public function comments()
{
return $this->morphMany(Comments::class, 'commentable');
}
}
Maintenant, pour récupérer les commentaires d’un Live, nous pouvons simplement appeler la méthode find avec l’id, et nous avons maintenant accès à la classe itérable comments :
<?php
use App\Models\Live;
$live = Live::find(1);
foreach ($live->comments as $comment) { }
// OR
Live::find(1)->comments()->each(function($comment) { // });
Live::find(1)->comments()->map(function($comment) { // });
Live::find(1)->comments()->filter(function($comment) { // });
// etc.
Et si nous avons le commentaire et que nous voulons savoir à qui il appartient, nous accédons à la méthode commentable :
<?php
use App\Models\Comment;
$comment = Comment::find(10);
$commentable = $comment->commentable;
// commentable – type of Post, Video, Poll, Live
Polymorphe un à d’autres
Dans de nombreuses applications qui évoluent, nous voulons un moyen facile d’interagir avec les modèles et entre eux. Nous pouvons vouloir connaître le premier ou le dernier message d’un utilisateur, ce qui peut être fait avec une combinaison de méthodes morphOne et ofMany :
<?php
public function latestPost()
{
return $this->morphOne(Post::class, 'postable')->latestOfMany();
}
public function oldestPost()
{
return $this->morphOne(Post::class, 'postable')->oldestOfMany();
}
Les méthodes latestOfMany et oldestOfMany récupèrent le modèle le plus récent ou le plus ancien en fonction de la clé primaire du modèle, qui était la condition pour qu’il puisse être trié.
Dans certains cas, nous ne voulons pas trier par l’ID, peut-être avons-nous changé la date de publication de certains articles et nous les voulons dans cet ordre, pas par leur ID.
Pour ce faire, vous pouvez passer deux paramètres à la méthode ofMany. Le premier paramètre est la clé par laquelle nous voulons filtrer, et le second est la méthode de tri :
<?php
public function latestPublishedPost()
{
return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max");
}
En gardant cela à l’esprit, il est possible de construire des relations plus avancées pour cela ! Imaginez le scénario suivant. On nous demande de générer une liste de tous les articles actuels dans l’ordre dans lequel ils ont été publiés. Le problème se pose lorsque deux articles ont la même valeur published_at et lorsque des articles sont programmés pour être publiés dans le futur.
Pour cela, nous pouvons passer l’ordre dans lequel nous voulons que les filtres soient appliqués à la méthode ofMany. De cette manière, nous classons les articles par published_at, et s’ils sont identiques, nous les classons par id. Deuxièmement, nous pouvons appliquer une fonction de requête à la méthode ofMany pour exclure tous les articles dont la publication est programmée !
<?php
public function currentPosts()
{
return $this->hasOne(Post::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}
Polymorphe plusieurs à plusieurs
Le many-to-many polymorphe est légèrement plus complexe que le normal. Une situation courante est celle où les balises s’appliquent à plusieurs actifs dans votre application. Par exemple, sur TikTok, nous avons des balises qui peuvent s’appliquer aux vidéos, aux courts métrages, aux histoires, etc.
La méthode polymorphe many-to-many nous permet d’avoir une seule table de balises associées aux vidéos, aux courts-métrages et aux histoires.
La structure du tableau est simple :
videos
id – integer
description – string
stories
id – integer
description – string
taggables
tag_id – integer
taggable_id – integer
taggable_type – string
Les tables étant prêtes, nous pouvons créer le modèle et utiliser la méthode morphToMany. Cette méthode accepte le nom de la classe du modèle et le « nom de la relation » :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
Grâce à cela, nous pouvons facilement définir la relation inverse. Nous savons que pour chaque modèle enfant, nous voulons appeler la méthode morphedByMany:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
public function stories()
{
return $this->morphedByMany(Story::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
Et maintenant, lorsque nous obtenons une balise, nous pouvons récupérer toutes les vidéos et les histoires liées à cette balise !
<?php
use App\Model\Tag;
$tag = Tag::find(10);
$posts = $tag->stories;
$videos = $tag->stories;
Optimisez Eloquent pour plus de rapidité
Lorsque vous travaillez avec l’ORM Eloquent de Laravel, il est essentiel de comprendre comment optimiser les requêtes de base de données et minimiser le temps et la mémoire nécessaires pour récupérer les données. L’une des façons d’y parvenir est d’implémenter la mise en cache dans votre application.
Laravel fournit un système de mise en cache flexible qui prend en charge différents backends, tels que Redis, Memcached et la mise en cache basée sur les fichiers. En mettant en cache les résultats des requêtes Eloquent, vous pouvez réduire le nombre de requêtes de base de données, ce qui rend votre application plus rapide et plus utile.
En outre, vous pouvez utiliser le constructeur de requêtes de Laravel pour créer des requêtes complexes supplémentaires, optimisant ainsi les performances de votre application.
En résumé
En conclusion, les relations Eloquent sont une fonctionnalité puissante de Laravel qui permet aux développeurs de travailler facilement avec des données liées. Qu’il s’agisse de relations un à un ou plusieurs à plusieurs, Eloquent fournit une syntaxe simple et intuitive pour définir et interroger ces relations.
En tant que développeur Laravel, la maîtrise des relations Eloquent peut considérablement améliorer votre flux de développement et rendre votre code plus efficace et plus lisible. Si vous souhaitez en savoir plus sur Laravel, Kinsta met à votre disposition diverses ressources, notamment un tutoriel sur la prise en main de Laravel et un article sur les salaires des développeurs Laravel.
Kinsta propose des solutions d’hébergement gérées qui facilitent le déploiement et la gestion des applications Laravel.
Laisser un commentaire