Module 13 – Base de données avec Eloquent
Niveau 3 – Laravel fondamental
Objectifs
Migrations, modèles Eloquent (CRUD, attributs, casts), relations en profondeur (belongsTo, hasMany, hasOne, belongsToMany, hasManyThrough), relations polymorphiques, eager loading et N+1, seeders et factories pour les données de test.
Théorie
Migrations
Les migrations décrivent les changements de schéma (création/modification de tables) de façon versionnée.
php artisan make:migration create_articles_table
Dans le fichier généré (database/migrations/...) :
public function up(): void
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body')->nullable();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void { Schema::dropIfExists('articles'); }
Exécuter : php artisan migrate. Annuler : php artisan migrate:rollback.
Modèles Eloquent
Un modèle = une table. Convention : modèle Article → table articles, clé primaire id.
php artisan make:model Article
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $fillable = ['title', 'body', 'user_id'];
protected $casts = ['published_at' => 'datetime'];
}
CRUD :
Article::all();
Article::find(1);
Article::where('user_id', 1)->get();
Article::create(['title' => 'My title', 'body' => '...', 'user_id' => 1]);
$article->update(['title' => 'New title']);
$article->delete();
Relations Eloquent – En profondeur
Une relation relie deux modèles via des clés (souvent clé étrangère). Eloquent suppose par défaut des noms de colonnes : user_id pour un belongsTo(User::class), table users, clé id. On peut surcharger ces conventions si besoin.
One-to-One : hasOne / belongsTo
Un enregistrement d’un modèle correspond à au plus un enregistrement d’un autre. Exemple : un User a un Profile.
// User has one Profile (table profiles avec user_id)
public function profile()
{
return $this->hasOne(Profile::class);
}
// Profile belongs to User
public function user()
{
return $this->belongsTo(User::class);
}
Convention : la table profiles contient user_id. Sinon : hasOne(Profile::class, 'foreign_key', 'local_key').
Utilisation :
$user->profile; // lazy
$user->profile()->create(['bio' => '...']);
One-to-Many : hasMany / belongsTo
Un modèle « parent » a plusieurs enregistrements « enfants ». Exemple : un User a plusieurs Articles ; un Article appartient à un User.
// User has many Articles
public function articles()
{
return $this->hasMany(Article::class);
}
// Article belongs to User
public function user()
{
return $this->belongsTo(User::class);
}
Convention : table articles avec colonne user_id. Options : hasMany(Article::class, 'foreign_key', 'local_key').
Utilisation :
$user->articles; // Collection
$user->articles()->where('published', true)->get();
$user->articles()->create(['title' => '...', 'body' => '...']);
$article->user; // User
$article->user_id = 2; $article->save(); // ou
$article->user()->associate($user); $article->save();
Many-to-Many : belongsToMany
Plusieurs enregistrements d’un modèle sont liés à plusieurs enregistrements d’un autre, via une table pivot. Exemple : Article et Tag (un article a plusieurs tags, un tag est sur plusieurs articles).
Table pivot : nom par ordre alphabétique des deux modèles singuliers : article_tag avec article_id et tag_id.
// Migration pivot : create_article_tag_table
Schema::create('article_tag', function (Blueprint $table) {
$table->foreignId('article_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->primary(['article_id', 'tag_id']);
});
// Article has many Tags (through pivot)
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// Tag has many Articles (through pivot)
public function articles()
{
return $this->belongsToMany(Article::class);
}
Si le nom de la pivot ou les clés diffèrent :
return $this->belongsToMany(Tag::class, 'article_tag', 'article_id', 'tag_id');
Utilisation :
$article->tags; // Collection de Tag
$article->tags()->attach($tagId);
$article->tags()->attach($tagId, ['order' => 1]); // pivot avec colonnes extra
$article->tags()->detach($tagId);
$article->tags()->sync([1, 2, 3]); // remplace la liste
$article->tags()->syncWithoutDetaching([1, 2]); // ajoute sans enlever
Colonnes supplémentaires sur la pivot : les déclarer dans la relation avec ->withPivot('order') et éventuellement ->withTimestamps().
Has Many Through (à travers un modèle intermédiaire)
Un modèle accède à des enregistrements d’un troisième modèle via un modèle intermédiaire. Exemple : Country → hasMany User → User hasMany Post ; on veut « les posts d’un pays ».
// Country has many Posts through Users
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
Convention : tables countries, users (avec country_id), posts (avec user_id). Sinon : hasManyThrough(Post::class, User::class, 'country_id', 'user_id').
Eager loading et problème N+1
Sans eager loading, afficher une liste d’articles avec le nom de l’auteur provoque une requête par article pour charger le user → N+1 (1 pour la liste + N pour les auteurs).
Solution : charger la relation en une seule requête avec with() :
$articles = Article::with('user')->get();
foreach ($articles as $article) {
echo $article->user->name; // pas de requête supplémentaire
}
Plusieurs relations :
Article::with(['user', 'tags'])->get();
Eager load imbriqué :
Article::with('comments.user')->get();
Lazy eager load (déjà une collection en mémoire) :
$articles->load('user');
Condition sur la relation chargée :
Article::with(['comments' => fn ($q) => $q->where('approved', true)])->get();
Relations polymorphiques
Une relation polymorphique permet à un modèle d’appartenir à plusieurs types de modèles différents via une seule association. Exemple typique : des Commentaires peuvent être attachés à un Article ou à une Video, sans dupliquer la logique.
Structure en base
Au lieu de article_id ou video_id, on utilise deux colonnes sur la table des commentaires :
commentable_id: l’ID de l’enregistrement (article ou video).commentable_type: le type (classe) du modèle, ex.App\Models\ArticleouApp\Models\Video.
// Migration comments
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->foreignId('user_id')->constrained();
$table->unsignedBigInteger('commentable_id');
$table->string('commentable_type');
$table->timestamps();
});
Côté modèle « enfant » : morphTo
Le modèle qui appartient à plusieurs types (ex. Comment) utilise morphTo :
// Comment.php
public function commentable()
{
return $this->morphTo();
}
Convention : colonnes commentable_id et commentable_type. Sinon : morphTo('commentable', 'type', 'id') (nom de la relation, colonne type, colonne id).
Côté modèles « parents » : morphMany / morphOne
Chaque modèle qui peut avoir des commentaires définit morphMany (ou morphOne si un seul) :
// Article.php
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// Video.php
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Utilisation :
$article->comments; // tous les commentaires de l'article
$article->comments()->create(['body' => '...', 'user_id' => 1]);
$comment->commentable; // Article ou Video selon commentable_type
$comment->commentable->title; // accès aux attributs du parent
Many-to-Many polymorphique : morphToMany / morphedByMany
Un modèle peut être lié à plusieurs types via une table pivot. Exemple : Tag peut être utilisé pour des Articles et des Videos.
Table pivot (ex. taggables) : tag_id, taggable_id, taggable_type.
// Tag.php : un tag peut être attaché à des articles ou des videos
public function articles()
{
return $this->morphedByMany(Article::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
// Article.php (et Video.php) : un article a plusieurs tags
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
Utilisation : comme un belongsToMany classique : $article->tags, $article->tags()->attach($tag), $tag->articles, etc.
Récapitulatif des relations
| Relation | Côté modèle | Exemple d’usage |
|---|---|---|
| belongsTo | Enfant → parent | Article appartient à User |
| hasOne | Parent → enfant | User a un Profile |
| hasMany | Parent → enfants | User a plusieurs Articles |
| belongsToMany | Les deux (pivot) | Article ↔ Tag (table article_tag) |
| hasManyThrough | Parent → petits-enfants | Country → Posts via Users |
| morphTo | Enfant polymorphique | Comment → Article ou Video |
| morphMany / morphOne | Parent | Article a plusieurs Comments |
| morphToMany / morphedByMany | Many-to-many polymorphique | Article ↔ Tag, Video ↔ Tag |
Seeders et Factories
Factory : génère des données fictives pour un modèle.
php artisan make:factory ArticleFactory
Seeder : remplit la base (appel aux factories ou insert manuel).
php artisan make:seeder ArticleSeeder
php artisan db:seed
Exemples rapides
hasOne / belongsTo : User → Profile (table profiles avec user_id).
hasMany / belongsTo : User → Articles (table articles avec user_id).
belongsToMany : Articles ↔ Tags via table pivot article_tag (article_id, tag_id).
Polymorphique : Comment morphTo commentable ; Article et Video morphMany Comment (colonnes commentable_id, commentable_type).
Quiz – Module 13
Q1. À quoi sert une migration ?
Q2. Quelle table et quelle clé primaire sont utilisées par défaut pour le modèle Article ?
Q3. Quelle méthode utiliser pour éviter le problème N+1 lors du chargement des relations ?
Q4. Quelle relation utiliser : « un article appartient à un utilisateur » ?
Q5. À quoi servent les factories ?
Q6. Pour une relation many-to-many entre Article et Tag, comment s’appelle la table pivot par convention ? Comment attacher/détacher un tag à un article ?
Q7. Qu’est-ce qu’une relation polymorphique ? Donner un exemple (modèle + colonnes en base).
Q8. Quelle méthode Eloquent utilise-t-on côté modèle « enfant » (ex. Comment) pour une relation polymorphique, et quelle(s) côté « parent » (ex. Article) ?
Réponses
R1. À décrire les changements de structure de la base (création/modification de tables) de façon versionnée et reproductible.
R2. Table articles, clé primaire id.
R3. with('relation') (eager loading) : ex. Article::with('user')->get().
R4. belongsTo dans le modèle Article vers User.
R5. À générer des données fictives pour les modèles (tests, seeders, développement).
R6. Table pivot par convention : article_tag (ordre alphabétique des noms singuliers), avec article_id et tag_id. Attacher : $article->tags()->attach($tagId) ; détacher : $article->tags()->detach($tagId) ; remplacer la liste : $article->tags()->sync([1, 2, 3]).
R7. Une relation polymorphique permet à un modèle d’appartenir à plusieurs types de modèles via une seule association. Exemple : Comment peut appartenir à Article ou Video ; en base : colonnes commentable_id (l’id) et commentable_type (la classe, ex. App\Models\Article).
R8. Côté enfant (Comment) : morphTo() (ex. commentable()). Côté parent (Article) : morphMany() ou morphOne() (ex. comments() avec morphMany(Comment::class, 'commentable')).
Suite
Module 14 – Authentification & Autorisation (Auth Laravel, guards, policies, gates).