HasMany dan BelongsTo: Relasi Satu-ke-Banyak
Relasi paling umum: satu user punya banyak post.
Relasi yang Paling Sering Kamu Tulis
Hampir semua aplikasi punya pola "satu punya banyak": satu user punya banyak post, satu post punya banyak komentar, satu kategori punya banyak produk. Dua method Eloquent ini adalah fondasi semua relasi lain. Kuasai ini sampai di luar kepala sebelum menyentuh relasi yang lebih eksotis seperti many-to-many.
Definisi di Kedua Sisi
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}hasMany dibaca "model ini punya banyak model lain", belongsTo dibaca "model ini milik satu model lain". Laravel menebak foreign key dari nama model: hasMany(Post::class) di User mencari kolom user_id di tabel posts. Kalau nama kolomnya tidak mengikuti konvensi, sebutkan eksplisit:
// app/Models/User.php
public function posts()
{
return $this->hasMany(Post::class, 'penulis_id', 'id');
}Argumen kedua adalah foreign key di tabel child, argumen ketiga adalah local key di tabel parent.
Memakai: Property vs Method (Penting!)
// app/Http/Controllers/UserController.php
$user = User::find(1);
foreach ($user->posts as $post) { // property: langsung query, hasilnya Collection
echo $post->title;
}
$terbaru = $user->posts() // method: query builder, bisa di-chain
->latest()
->take(5)
->get();Ini perbedaan yang paling sering membingungkan pemula. $user->posts (tanpa kurung) langsung mengeksekusi query dan mengembalikan Collection. $user->posts() (dengan kurung) mengembalikan objek relasi yang masih bisa di-chain where, orderBy, dan lain-lain. Mau filter atau batasi? Pakai kurung. Mau langsung loop semuanya? Tanpa kurung.
Membuat Data Lewat Relasi
// app/Http/Controllers/PostController.php
$user->posts()->create([
'title' => 'Post Baru',
'body' => 'Isi artikel...',
]); // user_id otomatis terisi dari $user->idIni pola paling bersih untuk membuat data child: foreign key diisi otomatis, tidak mungkin salah ketik atau lupa. Alternatifnya menyimpan lewat parent: $user->posts()->save($post).
Catatan teknis: belongsTo selalu mengembalikan satu model atau null, bukan Collection. Jadi $post->user->name bisa error kalau user_id-nya null atau user-nya sudah dihapus. Untuk relasi opsional, pakai nullsafe operator: $post->user?->name. Atau definisikan withDefault() pada relasi agar selalu ada objek pengganti: return $this->belongsTo(User::class)->withDefault(['name' => 'Anonim']); Dengan begitu view tidak perlu cek null di mana-mana.
Menghitung dan Mengecek Tanpa Memuat Semua
Relasi sebagai method memberi query builder penuh. Butuh angka tanpa memuat model? Panggil agregat langsung:
// app/Http/Controllers/UserController.php
$jumlah = $user->posts()->count(); // SELECT COUNT, tanpa memuat model
$adaBaru = $user->posts()->where('created_at', '>', now()->subDay())->exists();
$rataViews = $user->posts()->avg('views');Bandingkan dengan $user->posts->count(): itu memuat SEMUA post ke memori dulu baru dihitung. Untuk user dengan ribuan post, bedanya sangat besar. Aturan praktisnya: butuh datanya untuk ditampilkan, pakai property; butuh angka, boolean, atau pengecekan, pakai method dengan agregat.
Tantangan
Relasi Komentar
Buat relasi: Post hasMany Comment, Comment belongsTo Post (asumsi tabel comments ada dengan post_id). Via tinker: ambil 1 post, tampilkan jumlah komentarnya dan nama penulis post-nya.