Data dalam aplikasi nyata saling berhubungan: pengguna punya banyak artikel, artikel punya banyak komentar, artikel punya banyak tag dan tag dipakai banyak artikel. Eloquent (ORM Laravel) mengubah hubungan antar tabel ini menjadi method di model, sehingga kamu menulis $user->posts alih-alih query JOIN manual.
Pastikan kamu memahami primary key dan foreign key. Baca Memahami Primary Key dan Foreign Key.
Ringkasan jenis relasi
| Relasi | Contoh | Method di model |
|---|---|---|
| Satu-ke-satu | User ↔ Profil | hasOne / belongsTo |
| Satu-ke-banyak | User → banyak Post | hasMany / belongsTo |
| Banyak-ke-banyak | Post ↔ Tag | belongsToMany |
| Has-many-through | Negara → Post (lewat User) | hasManyThrough |
| Polymorphic | Komentar untuk Post atau Video | morphMany / morphTo |
1. Satu-ke-banyak (paling umum)
Foreign key berada di tabel "banyak".
// migration posts
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Pemakaian:
$user->posts; // koleksi semua post milik user
$user->posts()->where('published', true)->get(); // dengan kondisi (perhatikan tanda kurung)
$post->user->name; // pemilik post
Perbedaan penting: $user->posts mengembalikan hasil (koleksi), sedangkan $user->posts() mengembalikan query builder yang masih bisa dirangkai.
Laravel menebak nama foreign key dari nama model (user_id). Jika berbeda: hasMany(Post::class, 'penulis_id').
2. Satu-ke-satu
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
}
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Tambahkan ->unique() pada kolom user_id di tabel profiles agar database menjamin satu profil per pengguna. Gunakan withDefault() supaya $user->profile->bio tidak error saat profil belum ada:
return $this->hasOne(Profile::class)->withDefault(['bio' => '-']);
3. Banyak-ke-banyak
Butuh tabel pivot berisi dua foreign key. Konvensi nama: gabungan dua nama tabel tunggal berurutan alfabet, dipisah underscore: post_tag.
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->primary(['post_id', 'tag_id']);
});
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
}
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
Mengelola relasi:
$post->tags()->attach([1, 2]); // tambah
$post->tags()->detach(2); // lepas
$post->tags()->sync([1, 3, 4]); // jadikan persis ini (yang lain dilepas)
$post->tags()->toggle([1, 5]); // balik status
$post->tags()->syncWithoutDetaching([6]);
Pivot dengan kolom tambahan (misalnya peran atau urutan):
return $this->belongsToMany(Tag::class)->withPivot('urutan')->withTimestamps();
// akses: $tag->pivot->urutan
4. Has-many-through
Mengakses relasi "jauh" lewat model perantara. Contoh: Negara → User → Post.
class Negara extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}
5. Polymorphic
Satu tabel bisa berelasi dengan beberapa model berbeda. Contoh: tabel comments untuk Post dan Video.
$table->morphs('commentable'); // kolom commentable_id dan commentable_type
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Hemat tabel, tetapi database tidak bisa menerapkan foreign key pada kolom polimorfik, jadi integritas dijaga aplikasi.
Menyimpan data berelasi
// Dari sisi induk: foreign key terisi otomatis
$user->posts()->create(['title' => 'Halo', 'body' => '...']);
// Mengaitkan anak ke induk
$post->user()->associate($user)->save();
$post->user()->dissociate()->save();
// Membuat beberapa sekaligus
$user->posts()->createMany([...]);
Bungkus beberapa operasi berkaitan dalam DB::transaction() agar konsisten.
Query berdasarkan relasi
// Pengguna yang punya minimal satu post
User::has('posts')->get();
// Dengan syarat pada relasi
User::whereHas('posts', fn ($q) => $q->where('published', true))->get();
// Pengguna tanpa post
User::doesntHave('posts')->get();
// Menghitung relasi
User::withCount('posts')->orderByDesc('posts_count')->get();
Eager loading: wajib dipahami
Mengakses relasi di dalam perulangan tanpa eager loading menyebabkan N+1 query:
// Buruk: 1 + N query
foreach (Post::all() as $post) { echo $post->user->name; }
// Baik: 2 query
foreach (Post::with('user')->get() as $post) { echo $post->user->name; }
Bisa juga diatur otomatis lewat protected $with = ['user']; di model (hati-hati, ini selalu dimuat). Pembahasan mendalam ada di Tips Optimasi Query Laravel.
Aksi hapus dan integritas
- Atur di database:
cascadeOnDelete(),restrictOnDelete(), ataunullOnDelete()pada foreign key. - Atau di model, hapus relasi saat induk dihapus melalui event
deleting. - Untuk data penting gunakan soft delete (
SoftDeletes) agar relasinya tetap dapat dipulihkan.
Kesalahan umum
| Gejala | Penyebab |
|---|---|
Call to a member function ... on null | Relasi belongsTo belum ada (nilai null); gunakan ?-> atau withDefault() |
Column not found: user_id | Nama foreign key tidak mengikuti konvensi; sebutkan eksplisit |
| Relasi banyak-ke-banyak kosong | Nama tabel pivot tidak sesuai konvensi atau pivot kosong |
| Halaman lambat | N+1: lupa with() |
Memanggil $user->posts->where(...) lalu heran lambat | Itu memfilter koleksi di memori; pakai $user->posts()->where(...) agar difilter di database |
Latihan
Rancang sistem blog: User, Post, Category, Tag, Comment.
- Tentukan relasi tiap pasangan dan tabel apa yang butuh pivot.
- Tulis migration dengan foreign key dan aksi hapus yang tepat.
- Buat halaman daftar post yang menampilkan penulis, kategori, jumlah komentar, dan tag dengan hanya sedikit query.
Kesimpulan
Relasi Eloquent mengubah kolom foreign key menjadi navigasi objek yang intuitif. Kuasai lima jenis relasi, kelola pivot dengan sync/attach, selalu gunakan eager loading untuk daftar data, dan biarkan database menjaga integritas lewat foreign key.
Komentar (0)
Masuk / Daftar