Sejauh ini kita banyak membaca lewat relasi; sekarang saatnya menulis lewat relasi. Eloquent menyediakan method operasi relasi
yang membebaskan kita dari pengisian foreign key manual: save() dan create() untuk menambah anak pada relasi hasMany, associate() untuk menautkan induk pada belongsTo, serta keluarga attach/detach/sync untuk pivot table. Setiap method menempatkan nilai relasi pada tempatnya secara otomatis — dan beberapa di antaranya mengembalikan laporan yang layak dibaca. Semua contoh berikut dijalankan berurutan pada data yang sama, mulai dari post 3 yang belum punya komentar.
save() dan create() #
Method save() pada relasi menerima model yang sudah dibuat, mengisi foreign key-nya, lalu menyimpannya:
use App\Models\Comment;
use App\Models\Post;
$post = Post::find(3); // post tanpa komentar di data awal
$comment = new Comment(['isi' => 'Baru belajar, izin bertanya.', 'user_id' => 2]);
$post->comments()->save($comment);
printf("komentar #%d tersimpan | post_id=%d | user_id=%d\n",
$comment->id, $comment->post_id, $comment->user_id);Output yang Dihasilkan:
komentar #10 tersimpan | post_id=3 | user_id=2Setelah save(), kolom post_id pada model komentar terisi otomatis dari induknya — kita tidak pernah menulis post_id secara manual. Versi create() menggabungkan pembuatan model dan penyimpanan dalam satu panggilan:
$comment = $post->comments()->create(['isi' => 'Sudah coba, sukses!', 'user_id' => 3]);
printf("komentar #%d menempel di post %d\n", $comment->id, $comment->post_id);Output yang Dihasilkan:
komentar #11 menempel di post 3Perlu dicatat bahwa save() hanya mengurus foreign key milik relasi yang memanggilnya — kolom user_id pada contoh di atas tetap kita isi sendiri karena komentar dibuat lewat relasi comments, bukan lewat relasi user. Saudara lainnya, saveMany(), menerima array model sekaligus.
createMany() #
Untuk menyisipkan banyak baris berurutan, createMany() menerima array atribut:
$comments = $post->comments()->createMany([
['isi' => 'Komentar batch pertama.', 'user_id' => 1],
['isi' => 'Komentar batch kedua.', 'user_id' => 2],
]);
foreach ($comments as $comment) {
printf("komentar #%d: %s\n", $comment->id, $comment->isi);
}
echo 'komentar post 3 sekarang: ', $post->comments()->count(), "\n";Output yang Dihasilkan:
komentar #12: Komentar batch pertama.
komentar #13: Komentar batch kedua.
komentar post 3 sekarang: 4Method ini mengembalikan koleksi model yang baru dibuat, masing-masing sudah membawa post_id yang benar. Di belakang layar Eloquent memanggil create() berulang kali, sehingga event model tetap menyala untuk setiap baris.
associate() dan dissociate() #
Untuk relasi belongsTo, penautan induk dilakukan dari sisi anak. Komentar baru dibuat, lalu post dan penulisnya ditautkan sebelum disimpan:
$budi = User::find(1);
$comment = new Comment(['isi' => 'Komentar dari relasi belongsTo.']);
$comment->post()->associate($post);
$comment->user()->associate($budi);
$comment->save();
printf("komentar #%d oleh user %d di post %d\n",
$comment->id, $comment->user_id, $comment->post_id);Output yang Dihasilkan:
komentar #14 oleh user 1 di post 3Dua panggilan associate() mengisi post_id dan user_id tanpa menyentuh database — nilai ditulis ke atribut model, dan save() terjadi sekali di akhir. Kebalikannya, dissociate() mengosongkan foreign key di memori:
$comment->post()->dissociate();
echo 'post_id setelah dissociate: ', var_export($comment->post_id, true), "\n";
try {
$comment->save();
} catch (\Illuminate\Database\QueryException $e) {
echo 'save() gagal: ', substr($e->getMessage(), 0, 66), "\n";
}Output yang Dihasilkan:
post_id setelah dissociate: NULL
save() gagal: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constattach(), detach(), dan sync() pada belongsToMany #
Operasi pivot berjalan dari sisi relasi many-to-many. Kita pakai user Siti yang di data awal memegang role editor:
$siti = User::find(2);
$editor = Role::find(1);
$penulis = Role::find(2);
$siti->roles()->attach($penulis, ['added_by' => 3]);
echo 'setelah attach: ', $siti->fresh()->roles()->orderBy('roles.id')->pluck('name')->implode(', '), "\n";
$siti->roles()->detach($editor);
echo 'setelah detach: ', $siti->fresh()->roles()->pluck('name')->implode(', '), "\n";
$changes = $siti->roles()->sync([2 => ['added_by' => 3], 3 => ['added_by' => 1]]);
echo 'sync: ', json_encode($changes), "\n";
echo 'hasil akhir: ', $siti->fresh()->roles()->orderBy('roles.id')->pluck('name')->implode(', '), "\n";Output yang Dihasilkan:
setelah attach: editor, penulis
setelah detach: penulis
sync: {"attached":[3],"detached":[],"updated":[]}
hasil akhir: penulis, adminattach() menambah role penulis, detach() melepaskan editor, lalu sync() menyelaraskan sisa daftar: admin disisipkan dan added_by penulis diperbarui. Penjelasan lengkap kolom pivot beserta laporan attached/detached/updated sudah kita bedah di chapter Pivot Table
.
push(): Menyimpan Model Beserta Relasinya #
Kadang satu operasi harus menyimpan banyak sekaligus: revisi isi post sekaligus beberapa komentar barunya. Method push() menyimpan model dan seluruh relasi yang termuat di property relations-nya:
$post = Post::find(5);
$post->isi = 'Isi post kelima direvisi hari ini.';
$post->setRelation('comments', $post->comments()->makeMany([
['isi' => 'Komentar hasil makeMany pertama.', 'user_id' => 2],
['isi' => 'Komentar hasil makeMany kedua.', 'user_id' => 3],
]));
echo 'push(): ', var_export($post->push(), true), "\n";
echo 'isi terbaru di DB: ', $post->fresh()->isi, "\n";
foreach (Comment::where('post_id', 5)->orderByDesc('id')->get() as $comment) {
echo 'komentar #', $comment->id, ' (post_id=', $comment->post_id, '): ', $comment->isi, "\n";
}Output yang Dihasilkan:
push(): true
isi terbaru di DB: Isi post kelima direvisi hari ini.
komentar #16 (post_id=5): Komentar hasil makeMany kedua.
komentar #15 (post_id=5): Komentar hasil makeMany pertama.
komentar #9 (post_id=5): Nanti saya praktikkan ulang.Revisi isi post dan dua komentar baru tersimpan dari satu panggilan. Kunci pola ini ada di makeMany(): method tersebut membuat model komentar tanpa menyimpannya tetapi sudah mengisi post_id dari post yang sudah ada — dan push() menyimpan anak apa adanya tanpa mengisi foreign key. Anak yang foreign key-nya belum terisi akan gagal constraint NOT NULL saat di-push.
Sebagai rangkuman, tabel berikut memetakan operasi ke jenis relasi yang tepat:
| Operasi | Jenis relasi | Yang terjadi |
|---|---|---|
$post->comments()->save($c) | hasMany / hasOne | isi foreign key anak lalu simpan |
$post->comments()->create([...]) | hasMany / hasOne | buat model baru + isi foreign key + simpan |
$comment->post()->associate($p) | belongsTo | isi foreign key di memori tanpa query |
$user->roles()->attach($r) | belongsToMany | sisipkan baris pivot |
$user->roles()->sync([...]) | belongsToMany | selaraskan pivot sesuai daftar |
$post->push() | semua | simpan model + relasi termuat sekaligus |
Dengan rangkaian operasi ini, siklus hidup data lewat relasi sudah lengkap dari menautkan, menyimpan, sampai melepas. Bab berikutnya menutup section dengan topik yang menyatukan semuanya: mendeteksi dan mencegah masalah N+1 query — penyakit yang justru paling mudah lahir dari operasi-operasi nyaman di atas.