Model Post yang kita bangun di section Eloquent ORM selama ini berdiri sendiri: satu tabel, satu model, query selesai di tabel yang sama. Data aplikasi nyata hampir tidak pernah sesederhana itu — satu user menulis banyak post, satu post dikunjungi banyak comment, satu user bisa memegang banyak role. Database relasional menjawab kebutuhan ini dengan foreign key antar tabel, dan Eloquent menjawabnya dengan relationship: method pada model yang membungkus query lintas tabel menjadi satu ekspresi yang terbaca seperti kalimat. Section ini membahas setiap tipe relationship dari yang paling sederhana sampai polymorphic, dan chapter pembuka ini memetakan konsep-konsep dasarnya lebih dulu.

Data Nyata Selalu Terhubung #

Bayangkan satu tabel raksasa users_posts_comments_roles yang menyimpan nama user, judul post, isi comment, dan nama role dalam satu baris. Setiap user yang menulis post kedua akan menduplikasi seluruh datanya; mengganti satu nama berarti memperbarui ratusan baris. Karena itu kita memecah data per entitas — tabel users, posts, comments, roles — lalu menghubungkannya lewat foreign key seperti yang sudah kita latih di chapter Foreign Keys . Kolom posts.user_id menyatakan “baris post ini milik user dengan id tersebut”, dan database menjaga keabsahan rujukan itu dengan constraint.

Foreign key hidup di level database. Eloquent menambahkan lapisan di atasnya: asosiasi antar model yang ditulis sebagai method PHP, lengkap dengan perilaku bawaan untuk mengambil, membuat, dan menghubungkan data. Dokumentasi resminya berada di Eloquent Relationships , dan keenam tipe utamanya akan kita bahas satu per satu di section ini.

Peta Relationship di Eloquent #

Tabel berikut merangkum tipe relationship yang akan kita gunakan sepanjang section ini, lengkap dengan method pendefinisinya dan kolom kunci yang bekerja di belakangnya:

TipeMethodContoh pemakaianKolom kunci
One to onehasOne(Phone::class)User memiliki satu Phonephones.user_id
One to manyhasMany(Post::class)User menulis banyak Postposts.user_id
Inverse (many to one)belongsTo(User::class)Post ditulis satu Userposts.user_id
Many to manybelongsToMany(Role::class)User berbagi Role dengan user lainrole_user.role_id, role_user.user_id
Polymorphic one to manymorphMany(Comment::class, 'commentable')Post dan Video sama-sama punya Commentcomments.commentable_type, comments.commentable_id
Polymorphic many to manymorphToMany(Tag::class, 'taggable')Post dan Video sama-sama bertag Tagtaggables.taggable_type, taggables.taggable_id

Bentuk diagramnya kira-kira seperti ini, mencakup seluruh domain yang akan kita pakai dari chapter One to One sampai Inverse Relationships :

Perhatikan bahwa relasi many to many tampak sebagai dua garis one to many yang bertemu di tabel role_user — tabel perantara yang sering disebut pivot table. Sementara relasi polymorphic dari comments dan taggables tidak menggambar kolom commentable_type atau taggable_type secara eksplisit; kolom pasangan seperti itulah yang membedakan polymorphic dari relasi biasa.

Relationship adalah Method, Bukan Kolom #

Di migration, kolom didefinisikan sekali lalu selesai. Relationship bekerja berbeda: kita menambahkan method pada model yang mengembalikan objek relation. Tidak ada tabel atau kolom baru yang lahir dari method ini — yang dibuat hanyalah pintu dari satu model ke model lain:

php
$user = User::where('email', '[email protected]')->first();

echo get_class($user->phone()), "\n";   // pintu ke tabel phones
echo get_class($user->posts()), "\n";   // pintu ke tabel posts
echo get_class($user->roles()), "\n";   // pintu lewat tabel role_user

$phone = Phone::first();
echo get_class($phone->user()), "\n";   // pintu balik ke tabel users

Output yang Dihasilkan:

text
Illuminate\Database\Eloquent\Relations\HasOne
Illuminate\Database\Eloquent\Relations\HasMany
Illuminate\Database\Eloquent\Relations\BelongsToMany
Illuminate\Database\Eloquent\Relations\BelongsTo

Setiap tipe relationship adalah class berbeda — HasOne, HasMany, BelongsToMany, BelongsTo — dan semuanya turunan query builder. Karena objek relation membungkus query, kita bisa membongkar SQL yang dibangunnya. Di sinilah konvensi kunci Eloquent bekerja:

php
echo $user->phone()->toSql(), "\n";
echo $phone->user()->toSql(), "\n";
echo $user->roles()->toSql(), "\n";

Output yang Dihasilkan:

text
select * from "phones" where "phones"."user_id" = ? and "phones"."user_id" is not null
select * from "users" where "users"."id" = ?
select * from "roles" inner join "role_user" on "roles"."id" = "role_user"."role_id" where "role_user"."user_id" = ?

Mari kita baca ketiga SQL di atas sebagai bukti konvensi. Method phone() menebak bahwa kolom penghubung bernama phones.user_id — gabungan nama model User yang di-snake case ditambah akhiran _id. Method user() di model Phone menebak arah sebaliknya: kolom user_id milik phones dicocokkan dengan primary key users.id. Method roles() bahkan menebak nama tabel perantaranya, role_user, yaitu dua nama model yang diurutkan secara alfabetis lalu dijadikan satu nama tabel. Seluruh tebakan ini bisa kita ganti dengan argumen eksplisit saat skema tidak mengikuti konvensi, tetapi selama konvensi dipatuhi, definisi relasi cukup satu baris.

Lazy Loading: Baru Dikueri Saat Disentuh #

Method relasi belum menjalankan query apa pun. Query baru berjalan saat kita mengakses relasi sebagai property — $user->phone tanpa tanda kurung:

php
use Illuminate\Support\Facades\DB;

DB::enableQueryLog();

echo 'nomor Budi: ', $user->phone->nomor, "\n";

echo 'total query: ', count(DB::getQueryLog()), "\n";
foreach (DB::getQueryLog() as $q) {
    echo 'query: [', $q['query'], "]\n";
}

Output yang Dihasilkan:

text
nomor Budi: 0812-3456-7890
total query: 1
query: [select * from "phones" where "phones"."user_id" = ? and "phones"."user_id" is not null limit 1]

Perilaku ini disebut lazy loading: ekspresi $user->phone pada dasarnya adalah jalan pintas untuk Phone::where('user_id', $user->id)->first(), dieksekusi tepat pada baris itu juga dan hasilnya disimpan agar akses berikutnya tidak mengulang query. Kemudahan ini ada harganya — mengakses relasi di dalam loop memicu query beruntun yang dikenal sebagai masalah N+1 — dan strategi memuat relasi di muka akan kita bahas tuntas di chapter Eager Loading .

Dengan peta ini di tangan, kita siap mendefinisikan relationship pertama: relasi paling sederhana di mana satu user memiliki tepat satu nomor telepon.