Merangkai relasi antar model Eloquent: one-to-one, one-to-many, many-to-many, hasManyThrough, dan polymorphic, serta mengatasi masalah N+1 dengan eager loading with() dan withCount().

Database relasional hidup dari relasi antar tabel, dan Eloquent menerjemahkannya menjadi method PHP yang ekspresif. Di episode 8 kita sudah membuat posts.user_id sebagai foreign key; sekarang kita belajar bagaimana model Post dan User saling terhubung — dan bagaimana melakukannya tanpa menghancurkan performa dengan masalah N+1.
Kemampuan merancang relasi yang benar adalah pembeda antara aplikasi yang "jalan" dan aplikasi yang "jalan cepat dengan data yang benar".
Contoh klasik: satu User punya satu Profile.
public function profile()
{
return $this->hasOne(Profile::class);
}public function user()
{
return $this->belongsTo(User::class);
}$user->profile->bio;Arah kebalikannya selalu belongsTo — inilah "anak" yang memegang foreign key.
Post dimiliki oleh seorang user; user punya banyak post.
public function posts()
{
return $this->hasMany(Post::class);
}public function user()
{
return $this->belongsTo(User::class);
}$user->posts; // Collection
$post->user->name; // parent modelPerhatikan penamaan: relasi posts() jamak (hasMany), user() tunggal (belongsTo). Eloquent mengikuti convention ini untuk menebak foreign key — user_id untuk relasi user().
Post punya banyak tag, tag dipakai banyak post. Butuh tabel pivot.
php artisan make:migration create_post_tag_tableSchema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
});class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}$post->tags()->attach($tagId);
$post->tags()->sync([1, 2, 3]); // set exact setsync() mengganti seluruh relasi sekaligus — pola paling umum untuk form checkbox tag.
Lewati tabel penghubung. Contoh: kita ingin semua Post milik semua user di sebuah Country — lewat users.country_id.
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}Satu relasi dipakai banyak model. Contoh: Image bisa dimiliki oleh Post maupun User — tabel images menyimpan imageable_type (nama class) dan imageable_id.
class Image extends Model
{
public function imageable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
}Polymorphic sangat berguna untuk fitur attachment, komentar, atau like yang dipakai di banyak entitas berbeda.
Inilah performa killer yang paling sering muncul di aplikasi Laravel. Kode "polos" berikut:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // 1 query per post!
}Jika ada 100 post, ini menghasilkan 1 + 100 query — inilah masalah N+1. Solusinya eager loading dengan with():
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // 0 query tambahan
}$posts = Post::with(['user', 'tags'])->get();
$users = User::withCount('posts')->get();
echo $users->first()->posts_count;Sekarang total query menjadi 1 (posts) + 1 (users) + 1 (tags) = 3 query, berapa pun jumlah post.
Warning
Di development, aktifkan Laravel Query Logging atau package debug (Laravel Debugbar) untuk melihat jumlah query per request. Tanda klasik N+1: request yang sama menampilkan ratusan query identik hanya karena sebuah foreach — with() adalah obatnya.
| Jenis | Method | Kapan dipakai |
|---|---|---|
| One-to-one | hasOne / belongsTo | Profil user, detail 1-1 |
| One-to-many | hasMany / belongsTo | Post milik user |
| Many-to-many | belongsToMany (+ pivot) | Post & tag |
| Has-many-through | hasManyThrough | Akses lewat tabel perantara |
| Polymorphic | morphMany / morphTo | Lampiran di banyak entitas |
Inti yang harus dibawa pulang:
hasOne/hasMany di sisi "orang tua", belongsTo di sisi "anak" yang memegang foreign key.belongsToMany memakai tabel pivot; sync() untuk set relasi.morphMany/morphTo) untuk relasi multi-entitas.with() dan withCount() untuk koleksi.Di episode 10 selanjutnya kita akan membahas authentication — starter kit Breeze dan Jetstream, login/register/logout, middleware auth, password reset, dan email verification di Laravel 13. Sampai jumpa di episode 10!