Membuat data hanya separuh cerita — aplikasi nyata mengubah data terus-menerus: judul tulisan diedit, harga produk disesuaikan, draft akhirnya diterbitkan. Eloquent menyediakan dua jalur untuk memperbarui baris database: melalui instance model yang diambil lebih dulu, atau langsung lewat query yang menyasar banyak baris sekaligus. Keduanya tidak setara — jalur instance menjalankan event model beserta mutator, sedangkan jalur query memotong sebagian mekanisme tersebut. Chapter ini membedah kedua jalur, alat pelacak perubahan seperti isDirty() dan wasChanged(), sampai trik touch() untuk memperbarui timestamp saja. Sepanjang contoh kita memakai tabel posts dengan data uji ringkas — tiga baris: dua post milik Budi (Belajar Eloquent 150 views terbit, Latihan Migration 20 views draft) dan satu post milik Siti (Tips Query Builder 320 views terbit).
find() lalu save() #
Jalur pertama adalah yang paling eksplisit: ambil model dengan find() seperti di chapter Membaca Data
, ubah property-nya, lalu panggil save():
$post = Post::find(1);
echo $post->judul, ' | views=', $post->views, "\n";
$post->views = 175;
echo 'isDirty(): ', $post->isDirty() ? 'true' : 'false', "\n";
echo 'isDirty(\'views\'): ', $post->isDirty('views') ? 'true' : 'false', "\n";
echo 'getDirty(): ';
var_export($post->getDirty());
echo "\n";
echo 'Nilai di database masih: ', Post::find(1)->views, "\n";
$post->save();
echo 'wasChanged(\'views\'): ', $post->wasChanged('views') ? 'true' : 'false', "\n";
echo 'getChanges(): ';
var_export($post->getChanges());Output yang Dihasilkan:
Belajar Eloquent | views=150
isDirty(): true
isDirty('views'): true
getDirty(): array (
'views' => 175,
)
Nilai di database masih: 150
wasChanged('views'): true
getChanges(): array (
'views' => 175,
'updated_at' => '2026-01-15 09:00:00',
)Mari kita bedah baris-baris pentingnya:
$post->views = 175— Eloquent menyimpan nilai baru di memori; database belum tersentuh. Model yang punya perubahan belum tersimpan disebut dirty.isDirty()/isDirty('views')— melaporkan apakah model (atau kolom tertentu) berubah sejak diambil. Pemeriksaan kebalikannya adalahisClean().getDirty()— daftar kolom yang benar-benar akan dikirim keUPDATE, lengkap dengan nilainya.$post->save()— mengeksekusiUPDATE ... SET views = 175, updated_at = .... Setelah tersimpan,wasChanged('views')menjaditruedangetChanges()memuat kolom yang terakhir ditulis — termasukupdated_atyang diisi otomatis karena model memakai traitHasTimestamps.
update() dan fill() #
Menulis dua baris ($post->views = ...; $post->save()) terasa verbose untuk perubahan banyak kolom sekaligus. Method update([...]) memadatkan fill dan save menjadi satu panggilan:
$post = Post::find(2);
$post->update(['judul' => 'Latihan Migration Lanjutan', 'published' => true]);
$fresh = Post::find(2);
echo 'judul=', $fresh->judul, ' | published=', $fresh->published ? 'true' : 'false', "\n";
echo 'updated_at=', $fresh->updated_at, "\n";
$post->fill(['views' => 42]);
$post->save();
echo 'views=', Post::find(2)->views, "\n";Output yang Dihasilkan:
judul=Latihan Migration Lanjutan | published=true
updated_at=2026-01-15 09:00:00
views=42Method update() di atas setara dengan fill() lalu save(): updated_at ikut diperbarui dan event model tetap menyala. Baik update() maupun fill() menerapkan aturan mass assignment — hanya kolom yang terdaftar di $fillable yang boleh diisi lewat array, sebagaimana dibahas tuntas di chapter Mass Assignment
. Kapan memilih yang mana? update() cocok saat data sudah lengkap dalam satu array, misalnya hasil validasi form. fill() lebih tepat saat nilai dikumpulkan bertahap dari beberapa sumber sebelum akhirnya save() dipanggil sekali di akhir.
Memperbarui Banyak Baris Sekaligus #
Bayangkan harus menaikkan harga semua post milik satu penulis. Mengambil ribuan model satu per satu jelas boros. Jalur kedua adalah update() langsung pada query:
$jumlah = Post::where('user_id', 1)->update(['views' => 500]);
echo 'Baris terpengaruh: ', $jumlah, "\n";
echo 'Post #1: views=', Post::find(1)->views, ' | updated_at=', Post::find(1)->updated_at, "\n";
echo 'Post #2: views=', Post::find(2)->views, ' | updated_at=', Post::find(2)->updated_at, "\n";
$jumlah = DB::table('posts')->where('id', 3)->update(['views' => 999]);
echo 'Baris terpengaruh: ', $jumlah, "\n";
echo 'Post #3: views=', Post::find(3)->views, ' | updated_at=', Post::find(3)->updated_at, "\n";Output yang Dihasilkan:
Baris terpengaruh: 2
Post #1: views=500 | updated_at=2026-01-15 09:00:00
Post #2: views=500 | updated_at=2026-01-15 09:00:00
Baris terpengaruh: 1
Post #3: views=999 | updated_at=2026-01-10 08:00:00Output ini membuktikan dua fakta penting. Pertama, query lewat model tetap menambahkan updated_at otomatis — kedua post Budi mendapat 2026-01-15 09:00:00. Kedua, query builder mentah lewat DB::table() tidak menyentuh updated_at sama sekali; post milik Siti berubah views-nya tetapi timestamp-nya masih nilai lama 2026-01-10 08:00:00.
Alasan performa juga layak disebut: mass update mengirim satu statement UPDATE untuk semua baris yang cocok, sedangkan loop update() per instance mengirim satu SELECT plus satu UPDATE untuk setiap model. Untuk puluhan ribu baris, selisihnya sangat terasa — inilah alasan jalur ini tetap disediakan.
touch() untuk Timestamp Saja #
Kadang kita hanya ingin menandai “baris ini sempat disentuh” tanpa mengubah kolom apa pun. Method touch() mengisi updated_at dengan waktu sekarang lalu menyimpannya:
$post = Post::find(3);
echo 'updated_at sebelum: ', $post->updated_at, "\n";
Carbon::setTestNow(Carbon::parse('2026-01-16 10:00:00'));
$post->touch();
echo 'updated_at sesudah: ', $post->fresh()->updated_at, "\n";Output yang Dihasilkan:
updated_at sebelum: 2026-01-10 08:00:00
updated_at sesudah: 2026-01-16 10:00:00Hanya updated_at yang berubah — judul, views, dan kolom lain tidak tersentuh. Method ini juga menerima nama kolom bila model punya timestamp tambahan, misalnya touch('dilihat_pada') untuk mengisi kolom tersebut dengan waktu sekarang. Pola ini umum dipakai untuk cache invalidation atau mengurutkan konten berdasarkan aktivitas terakhir; dokumentasi Eloquent
menyebutnya touching.
Kita kini menguasai tiga dari empat operasi CRUD — tinggal satu yang belum dibedah. Menghapus data punya jebakannya sendiri: id yang sudah hilang, operasi massal tanpa kondisi, dan tabel yang dikosongkan total. Chapter berikutnya mengupas delete(), destroy(), dan truncate() sampai tuntas.