Hari ini saya lanjut ke materi yang sudah saya nantikan sejak belajar Eloquent ORM di episode sebelumnya: relationship, atau relasi antar-tabel. Di episode #6 saya sudah mengenal model sebagai representasi satu tabel, tapi dalam dunia nyata database tidak pernah hanya satu tabel yang berdiri sendiri. Ada user yang punya banyak post, ada post yang milik satu user, ada tag yang bisa dipakai di banyak post — dan Eloquent punya cara yang sangat elegan untuk menghubungkan semuanya.
Masalah yang dijawab oleh relasi
Pernahkah saya menulis kode seperti ini di PHP murni?
<?php
// Ambil semua post
$posts = DB::select('SELECT * FROM posts');
// Untuk setiap post, ambil nama penulisnya
foreach ($posts as $post) {
$author = DB::select('SELECT name FROM users WHERE id = ?', [$post->user_id]);
$post->author_name = $author[0]->name ?? 'Unknown';
}
Ini yang disebut N+1 problem: 1 query untuk ambil semua post, lalu N query lagi untuk ambil data user di setiap post. Kalau ada 100 post, berarti 101 query database. Padahal seharusnya cukup 2 query saja.
Eloquent relasi menyelesaikan masalah ini. Tapi bukan hanya soal performa — relasi juga membuat kode lebih readable. Daripada menulis WHERE user_id = ? di banyak tempat, cukup tulis $post->author dan Laravel tahu harus mengambil data dari mana.
Empat relasi dasar yang saya pelajari
Laravel mendukung empat jenis relasi utama.

Saya akan bahas satu per satu dengan contoh skenario yang dekat dengan kebutuhan nyata.
1. hasOne — “punya satu”
Bayangkan sebuah User punya satu Profile. Satu user, satu profil. Ini relasi 1:1. Di database, tabel profiles punya kolom user_id yang menunjuk ke user pemiliknya.
Di model User, saya cukup mendeklarasikan:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Model
{
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
}
Sekarang di controller atau view, cukup tulis $user->profile — Laravel otomatis mengambil data profil dari tabel profiles berdasarkan user_id. Sama seperti di dunia nyata: “User A punya profil, siapa profilnya? Nanti saya tanya.”
2. hasMany — “punya banyak”
Sebaliknya, satu User bisa punya banyak Post. Ini relasi 1:N, dan saya sering menemukannya di hampir setiap aplikasi.
<?php
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
Gunanya: $user->posts mengembalikan koleksi semua post yang dimiliki user tersebut. Saya bisa langsung loop:
<?php
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
Ini seperti membalik halaman buku: dari satu user, saya bisa membaca semua cerita yang pernah ia tulis. Di database sebenarnya hanya satu query yang dijalankan, bukan N+1.
3. belongsTo — “milik siapa”
Relasi ini adalah kembaran dari hasMany — kalau User punya banyak Post, maka setiap Post milik satu User. Di sisi Post, saya mendeklarasikan:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Post extends Model
{
public function author(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Yang saya pelajari: method-nya dinamai author(), bukan user(). Ini pilihan saya sendiri — $post->author lebih terasa natural daripada $post->user di kode view. Tapi kalau saya mau tetap pakai user, cukup ganti nama method-nya. Yang penting: Eloquent tahu mencari kolom user_id di tabel posts karena konvensi penamaan yang sudah dipelajari di episode #6.
4. belongsToMany — “hubungan banyak ke banyak”
Ini relasi yang paling menarik sekaligus yang paling sering bikin saya bingung. Skenarionya: satu Post bisa punya banyak Tag, dan satu Tag bisa dipakai di banyak Post. Hubungan ini butuh tabel pivot — tabel perantara yang menyimpan pasangan post_id dan tag_id.
<?php
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Laravel otomatis mencari tabel post_tag (gabungan nama kedua tabel, diurutkan secara alfabetis). Kalau saya ingin nama tabel pivot sendiri, cukup parameter kedua:
<?php return $this->belongsToMany(Tag::class, 'post_tags');
Saya perlu membiasakan diri: tabel pivot itu bukan tabel model — tidak ada PostTag.php. Tabel pivot hanya menyimpan hubungan antar-dua entitas. Tapi kalau saya butuh kolom ekstra di pivot (misalnya order atau date_assigned), saya bisa pakai withPivot():
<?php
return $this->belongsToMany(Tag::class)->withPivot('created_at');
Definisi di kedua sisi — jangan lupa
Hal yang sempat bikin saya bingung: relasi harus didefinisikan di kedua model. Kalau User punya many Post, maka di model User saya tulis hasMany(Post::class), DAN di model Post saya tulis belongsTo(User::class). Kalau hanya ada satu sisi, relasi tidak akan bekerja dengan benar.
Bayangkan ini seperti tali: ujung satu dipegang oleh User, ujung lain dipegang oleh Post. Kalau hanya satu ujung yang dipegang, tali itu jatuh ke tanah dan tidak menghubungkan apa pun.
Eager loading: cara cerdas hindari N+1
Meskipun saya sudah mendefinisikan relasi, masih ada jebakan kalau saya tidak hati-hati. Contoh:
<?php
// N+1 problem tersembunyi!
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name;
}
Pertama, Post::all() mengambil semua post. Tapi saat saya mengakses $post->author, Laravel harus mengambil data user untuk masing-masing post secara terpisah. Ini N+1 yang sama seperti contoh awal tadi.
Solusinya: eager loading. Saya minta Laravel mengambil data relasi sejak awal, bukan nanti saat diakses:
<?php
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name;
}
Cukup tambahkan with('author') sebelum get(), Laravel akan menjalankan 2 query saja: satu untuk semua post, satu untuk semua user yang relevan. Kode saya tetap sama — $post->author->name — tapi di balik layar query-nya jauh lebih efisien.
Ini pola yang saya catat sebagai aturan: kalau saya akan mengakses relasi di dalam loop, selalu pakai with(). Tanpa with(), saya sedang diam-diam membuat server bekerja 100 kali lebih keras dari yang seharusnya.
Rangkuman 4 relasi (dalam tabel sederhana)
Supaya tidak lupa, saya buat ringkasan ini untuk diri sendiri:
| Jenis Relasi | Contoh Skenario | Metode di Model |
|---|---|---|
| hasOne | User → Profile (1:1) | $this->hasOne(Profile::class) |
| hasMany | User → Post (1:N) | $this->hasMany(Post::class) |
| belongsTo | Post → User (N:1) | $this->belongsTo(User::class) |
| belongsToMany | Post ↔ Tag (N:M) | $this->belongsToMany(Tag::class) |
Yang saya pelajari dari episode ini
Eloquent relasi memberikan dua manfaat utama: kode yang lebih mudah dibaca dan query yang lebih efisien. Saya tidak perlu menulis SQL join yang panjang atau query berulang di dalam loop — cukup definisikan method relasi di model, lalu akses seperti property biasa.
Tapi yang paling penting saya catat adalah: eager loading. Tanpa with(), relasi di dalam loop itu jebakan performa yang tersembunyi — dan saya tidak ingin baru sadar setelah aplikasi saya melambat di production. Saya tulis ini sebagai catatan untuk diri sendiri: kalau loop, with().
Di episode berikutnya saya akan lanjut ke Form Handling & Validasi — belajar cara memproses data dari form dengan aman dan menampilkan error dengan rapi di Blade. Sampai ketemu!
Komentar Terbaru