Semua Jenis Relasi Eloquent — dengan Contoh Kasus Nyata

Salah satu hal yang bikin Laravel terasa menyenangkan adalah Eloquent. Kamu tidak perlu nulis JOIN manual — cukup definisikan hubungan antar model, dan Laravel yang urus sisanya.

Tapi ada tujuh jenis relasi, dan masing-masing punya konteks pemakaian yang berbeda. Kalau dipelajari satu-satu tanpa gambaran besarnya, mudah sekali bingung — terutama ketika sampai di bagian polymorphic.

Jadi kita mulai dari yang paling simpel, naik pelan-pelan ke yang paling kompleks. Untuk setiap relasi, kita pakai satu kasus nyata yang konkret supaya mudah dibayangkan.


🧭 Peta Relasi Sebelum Mulai

Sebelum masuk ke kode, ini gambaran besar ketujuh relasi berdasarkan strukturnya:

Satu ke Satu           hasOne / belongsTo
Satu ke Banyak         hasMany / belongsTo
Banyak ke Banyak       belongsToMany (dua arah)
Satu ke Banyak Lewat   hasManyThrough
Polymorphic 1:1 / 1:N  morphOne / morphMany / morphTo
Polymorphic N:N        morphToMany / morphedByMany

1️⃣ One to One — hasOne & belongsTo

Kasus nyata

Satu User punya tepat satu Profile. Satu Profile hanya milik satu User.

Ini biasanya terjadi ketika kamu memisahkan data autentikasi (email, password) dari data profil (bio, avatar, nomor telepon) — dua concern yang berbeda, jadi dua tabel yang berbeda.

Struktur tabel

users
  id (PK)
  email

profiles
  id (PK)
  user_id (FK → users.id)   ← foreign key ada di sisi "anak"
  bio
  avatar

Kode model

📁 app/Models/User.php

class User extends Model
{
    // "Saya punya satu profile di tabel lain"
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }
}

📁 app/Models/Profile.php

class Profile extends Model
{
    // "Saya punya foreign key — saya milik seorang User"
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

Cara pakainya

// Ambil profile dari user
$profile = User::find(1)->profile;

// Ambil user pemilik profile
$user = Profile::find(1)->user;

📌 Aturan mudah: Foreign key (user_id) selalu ada di tabel yang belongsTo. Kalau bingung mana yang hasOne dan mana yang belongsTo — cari tabel mana yang punya kolom _id-nya, itulah yang belongsTo.


2️⃣ One to Many — hasMany & belongsTo

Kasus nyata

Satu Post bisa punya banyak Comment. Tapi satu Comment hanya milik satu Post.

Ini relasi paling umum. Hampir semua entitas “induk–anak” cocok dengan pola ini: User → Orders, Category → Products, Invoice → Items.

Struktur tabel

posts
  id (PK)
  title

comments
  id (PK)
  post_id (FK → posts.id)   ← foreign key tetap di sisi "anak"
  body
  user_id

Kode model

📁 app/Models/Post.php

class Post extends Model
{
    // "Saya punya banyak komentar"
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

📁 app/Models/Comment.php

class Comment extends Model
{
    // "Saya milik sebuah Post"
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

Cara pakainya

// Semua komentar dari sebuah post
$comments = Post::find(1)->comments;

// Post mana yang dimiliki komentar ini
$post = Comment::find(1)->post;

// Buat komentar baru langsung lewat relasi
Post::find(1)->comments()->create([
    'body'    => 'Artikel yang bagus!',
    'user_id' => auth()->id(),
]);

📌 Perbedaan ->comments (tanpa kurung) dan ->comments() (dengan kurung): yang pertama langsung mengembalikan koleksi, yang kedua mengembalikan query builder — bisa di-chain dengan where(), orderBy(), dll.


3️⃣ Many to Many — belongsToMany

Kasus nyata

Seorang User bisa punya banyak Role (admin, editor, viewer). Dan satu Role bisa dimiliki oleh banyak User.

Ini tidak bisa diselesaikan hanya dengan dua tabel — karena tidak ada yang bisa menaruh foreign key di satu pihak saja. Dibutuhkan tabel pivot sebagai perantara.

Struktur tabel

users
  id (PK)

roles
  id (PK)
  name

role_user          ← tabel pivot (urutan alfabetis, snake_case)
  user_id (FK → users.id)
  role_id (FK → roles.id)

Kode model

📁 app/Models/User.php

class User extends Model
{
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
        // Laravel otomatis cari tabel pivot 'role_user'
    }
}

📁 app/Models/Role.php

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}

Cara pakainya

$user = User::find(1);

// Lampirkan role ke user (INSERT ke tabel pivot)
$user->roles()->attach($roleId);

// Lepas role dari user (DELETE dari tabel pivot)
$user->roles()->detach($roleId);

// Sinkronisasi — setel ulang pivot agar hanya berisi ID ini
// Role yang tidak ada di array akan otomatis dilepas
$user->roles()->sync([1, 3, 5]);

// Ambil semua role milik user
foreach ($user->roles as $role) {
    echo $role->name;
}

📌 Kalau tabel pivot punya kolom tambahan (misalnya assigned_at, level), akses lewat properti pivot:

// Definisikan kolom ekstra di model
return $this->belongsToMany(Role::class)->withPivot('assigned_at');

// Akses nilainya
echo $user->roles->first()->pivot->assigned_at;

4️⃣ Has Many Through — hasManyThrough

Kasus nyata

Sebuah Country ingin mengambil semua Post yang ditulis oleh warganya. Tapi Country tidak punya relasi langsung ke Post — hanya punya relasi ke User, dan User yang punya relasi ke Post.

Ini yang disebut relasi “lewat” — kamu butuh model perantara untuk menyambungkan dua model yang tidak terhubung langsung.

Struktur tabel

countries
  id (PK)
  name

users
  id (PK)
  country_id (FK → countries.id)

posts
  id (PK)
  user_id (FK → users.id)
  title

Kode model

📁 app/Models/Country.php

class Country extends Model
{
    // "Saya punya banyak Post, lewat User"
    public function posts(): HasManyThrough
    {
        return $this->hasManyThrough(
            Post::class,   // model tujuan akhir
            User::class    // model perantara
        );
    }
}

Cara pakainya

// Semua post dari user yang berasal dari Indonesia
$posts = Country::where('name', 'Indonesia')->first()->posts;

📌 hasManyThrough tidak butuh kode tambahan di model User atau Post. Cukup definisikan di model Country, Laravel yang urus query-nya secara otomatis.


5️⃣ Polymorphic One to Many — morphMany & morphTo

Kasus nyata

Fitur komentar di aplikasimu bisa menempel ke Post maupun ke Video. Kamu tidak mau bikin tabel post_comments dan video_comments yang strukturnya identik — itu duplikasi.

Polymorphic adalah solusinya: satu tabel comments yang bisa “menempel” ke model mana saja.

Struktur tabel

comments
  id (PK)
  body
  commentable_id    ← ID dari model induknya (Post atau Video)
  commentable_type  ← nama class model induknya ('App\Models\Post')

Kode model

📁 app/Models/Post.php

class Post extends Model
{
    // "Saya bisa punya banyak komentar (polymorphic)"
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

📁 app/Models/Video.php

class Video extends Model
{
    // Persis sama — polymorphic bisa ditempel ke model apa saja
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

📁 app/Models/Comment.php

class Comment extends Model
{
    // "Saya bisa milik model apa saja — Post, Video, atau yang lain"
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

Cara pakainya

// Tambah komentar ke post
Post::find(1)->comments()->create(['body' => 'Mantap!']);

// Tambah komentar ke video — kode yang sama persis
Video::find(1)->comments()->create(['body' => 'Keren!']);

// Dari komentar, ambil model induknya (entah Post atau Video)
$comment = Comment::find(1);
$parent = $comment->commentable; // bisa berupa Post atau Video

📌 commentable adalah nama bebas — itu yang disebut morph name. Harus konsisten antara nama di kolom tabel (commentable_id, commentable_type) dan nama yang dipakai di morphMany('commentable') dan morphTo().


6️⃣ Polymorphic Many to Many — morphToMany & morphedByMany

Kasus nyata

Fitur Tag di aplikasimu bisa dipakai di Post, Video, maupun Product. Dan satu konten bisa punya banyak tag. Ini Many-to-Many, tapi polymorphic — karena tag bisa menempel ke model yang berbeda-beda.

Struktur tabel

tags
  id (PK)
  name

taggables          ← tabel pivot polymorphic
  tag_id (FK → tags.id)
  taggable_id      ← ID dari Post, Video, atau Product
  taggable_type    ← nama class-nya

Kode model

📁 app/Models/Post.php

class Post extends Model
{
    public function tags(): MorphToMany
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

📁 app/Models/Tag.php

class Tag extends Model
{
    // Dari Tag, ambil semua Post yang pakai tag ini
    public function posts(): MorphedByMany
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    // Dari Tag, ambil semua Video yang pakai tag ini
    public function videos(): MorphedByMany
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

Cara pakainya

// Tempel tag ke post
Post::find(1)->tags()->attach($tagId);

// Ambil semua post dengan tag tertentu
$posts = Tag::where('name', 'laravel')->first()->posts;

🗺️ Gambaran Besar — Kapan Pakai Yang Mana

Pertanyaan pertama: berapa "banyak" di setiap sisi?
│
├── Satu ↔ Satu
│    └── hasOne + belongsTo
│        Contoh: User ↔ Profile
│
├── Satu ↔ Banyak
│    └── hasMany + belongsTo
│        Contoh: Post → Comments
│
├── Banyak ↔ Banyak
│    └── belongsToMany (butuh tabel pivot)
│        Contoh: User ↔ Roles
│
├── Tidak ada relasi langsung, lewat model lain
│    └── hasManyThrough
│        Contoh: Country → User → Posts
│
└── Relasi bisa menempel ke banyak model berbeda?
     │
     ├── Satu / Banyak → morphOne / morphMany + morphTo
     │   Contoh: Post & Video → Comments
     │
     └── Banyak ↔ Banyak → morphToMany + morphedByMany
         Contoh: Post & Video & Product ↔ Tags

📊 Perbandingan Singkat

RelasiMethodKebutuhan Tabel TambahanPolymorphic
One to OnehasOne / belongsTo
One to ManyhasMany / belongsTo
Many to ManybelongsToMany✅ pivot table
Has Many ThroughhasManyThrough
Poly One to ManymorphMany / morphTo❌ (2 kolom ekstra)
Poly Many to ManymorphToMany / morphedByMany✅ pivot polymorphic

💡 Ingat!

Dua hal yang paling sering bikin bingung di relasi Eloquent:

PertamabelongsTo selalu ada di model yang punya foreign key di tabelnya. Kalau kamu lihat kolom user_id di tabel posts, maka Post yang punya belongsTo(User::class) — bukan sebaliknya.

Kedua — Polymorphic bukan sesuatu yang perlu dipaksakan di setiap kasus. Pakai polymorphic hanya ketika satu entitas benar-benar perlu “menempel” ke beberapa model berbeda secara bersamaan. Kalau tidak ada kebutuhan itu, relasi biasa jauh lebih mudah dibaca dan di-debug.

Dan satu lagi — selalu gunakan eager loading (with()) ketika mengambil relasi dalam loop. Topik itu layak artikel sendiri, tapi intinya: Post::with('comments')->get() jauh lebih baik daripada memanggil $post->comments di dalam foreach.