Les relations Eloquent de Laravel vous permettent d'exprimer les liens entre vos tables de base de données à travers des objets PHP lisibles et élégants. Les commentaires d'un article de blog, les commandes d'un utilisateur, les produits d'une catégorie… au lieu d'écrire des requêtes JOIN à la main pour gérer ces liens, vous les définissez avec une seule méthode sur vos modèles Eloquent. Dans cet article, nous aborderons les deux relations les plus courantes — hasMany et belongsTo — avec des exemples, avant de passer aux autres types de relations et à l'eager loading, la colonne vertébrale des performances relationnelles.
Aperçu des types de relations
Eloquent vous permet de déclarer les modèles liés sous forme de méthodes. Dans une application typique, vous rencontrerez ces relations :
- Un-à-un (
hasOne/belongsTo) : un utilisateur possède un seul profil. - Un-à-plusieurs (
hasMany/belongsTo) : un article possède plusieurs commentaires. - Plusieurs-à-plusieurs (
belongsToMany) : un article a plusieurs étiquettes, et une étiquette appartient à plusieurs articles. - Relations distantes et polymorphiques : des scénarios plus avancés comme
hasManyThroughetmorphMany.
Cet article se concentre sur la relation un-à-plusieurs, pierre angulaire de l'ensemble, car hasMany et son pendant belongsTo fonctionnent de pair.
hasMany : « un enregistrement en possède plusieurs »
L'exemple classique est un blog : un Post contient plusieurs Comment. Côté base de données, la table comments possède une colonne post_id. Cette clé étrangère indique à quel article appartient chaque commentaire.
// app/Models/Post.php
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Par défaut, Eloquent suppose que la clé étrangère est le nom du modèle parent (au singulier) + _id, soit post_id. Si votre colonne porte un autre nom, vous pouvez le préciser explicitement avec les deuxième et troisième arguments :
return $this->hasMany(Comment::class, 'post_id', 'id');
Vous pouvez désormais accéder aux commentaires d'un article comme s'il s'agissait d'une propriété :
$post = Post::find(1);
foreach ($post->comments as $comment) {
echo $comment->body;
}
Appeler $post->comments sans parenthèses renvoie une Collection, tandis que $post->comments() renvoie un constructeur de requêtes. Ce dernier vous permet d'affiner la relation :
$approved = $post->comments()
->where('approved', true)
->latest()
->get();
belongsTo : l'autre extrémité de la relation
belongsTo est l'inverse de hasMany. Placez-vous du point de vue du commentaire : chaque commentaire appartient à un article. Le côté qui détient la clé étrangère — la table comments avec sa colonne post_id — est toujours le côté belongsTo.
// app/Models/Comment.php
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
Cela vous permet de remonter facilement d'un commentaire vers son article parent :
$comment = Comment::find(5);
echo $comment->post->title;
Définir les deux côtés est une bonne pratique : hasMany sur le Post et belongsTo sur le Comment. Vous pouvez ainsi parcourir la relation dans les deux sens et clarifier votre intention pour quiconque lit le code.
Créer des enregistrements via une relation
Les relations Eloquent ne servent pas qu'à la lecture ; vous pouvez créer de nouveaux enregistrements avec la clé étrangère remplie automatiquement. Les méthodes save et create définissent post_id à votre place :
$post = Post::find(1);
$post->comments()->create([
'body' => 'Excellent article, merci !',
'author' => 'Camille',
]);
Pour utiliser create, n'oubliez pas d'ajouter les champs concernés au tableau $fillable du modèle. Dans l'autre sens, utilisez associate pour rattacher un commentaire existant à un article :
$comment->post()->associate($post);
$comment->save();
Le problème N+1 et l'eager loading
L'erreur la plus fréquente avec les relations est un piège de performance discret : le problème de requêtes N+1. Imaginez accéder à l'auteur de chaque article dans une boucle :
$posts = Post::all(); // 1 requête
foreach ($posts as $post) {
echo $post->author->name; // 1 requête de plus par article
}
Avec 50 articles, cela représente 1 + 50 = 51 requêtes. La solution est l'eager loading : avec la méthode with, vous chargez la relation à l'avance en une seule requête supplémentaire.
$posts = Post::with('author')->get(); // 2 requêtes au total
foreach ($posts as $post) {
echo $post->author->name; // aucune requête supplémentaire
}
En coulisses, Eloquent exécute une requête WHERE ... IN (...) pour récupérer tous les auteurs d'un coup. Vous pouvez aussi charger des relations imbriquées et multiples :
$posts = Post::with(['author', 'comments.user'])->get();
Pour repérer cette erreur tôt en développement, vous pouvez interdire le lazy loading avec Model::preventLazyLoading(), ou surveiller le nombre de requêtes avec un outil comme Laravel Debugbar. Si vous n'avez besoin que du nombre d'enregistrements liés, utilisez withCount au lieu de charger toutes les lignes en mémoire :
$posts = Post::withCount('comments')->get();
echo $posts->first()->comments_count;
Conseils pratiques
- Ajoutez toujours un index de base de données sur les colonnes de clé étrangère ; cela accélère sensiblement les requêtes de relation.
- Dans les migrations, utilisez
foreignId('post_id')->constrained()->cascadeOnDelete()pour créer à la fois l'index et une contrainte d'intégrité. - Choisissez des noms de méthode de relation pertinents, cohérents au singulier/pluriel : pluriel pour
hasMany(comments), singulier pourbelongsTo(post). - Sur les écrans de liste, utilisez presque toujours
with; sur une page de détail d'un seul enregistrement, ne chargez que le nécessaire.
Questions fréquentes
Quelle est la différence entre hasMany et belongsTo ?
hasMany se définit du côté « parent » de la relation et représente plusieurs enregistrements enfants. belongsTo se trouve du côté « enfant » qui détient la clé étrangère et pointe vers un seul enregistrement parent. Ce sont les deux directions d'une même relation.
Puis-je utiliser un nom de colonne de clé étrangère différent de l'hypothèse d'Eloquent ?
Oui. Passez la clé étrangère en deuxième argument et la clé locale en troisième : hasMany(Comment::class, 'article_id', 'id'). Sauf indication contraire, Eloquent suppose le nom du modèle au singulier + _id.
Faut-il toujours utiliser l'eager loading ?
Si vous accédez aux données liées dans une boucle, oui — cela évite le problème N+1. Mais si vous ne touchez jamais à la relation, ne la chargez pas inutilement ; cela ajoute des requêtes et de la consommation mémoire. Adaptez l'équilibre à vos besoins.
Bien modéliser les relations est la base d'une application Laravel rapide et maintenable. Si vous avez besoin d'aide pour la modélisation Eloquent, l'optimisation N+1 ou le développement complet d'un backend Laravel, contactez-moi et posons ensemble des fondations solides.