Tidak semua data layak disimpan selamanya: komentar spam, post draft yang dibuang, atau transaksi gagal. Menghapus di Eloquent tampak sederhana, tetapi tiga pertanyaan praktis selalu mengikutinya — bagaimana menghapus tanpa mengambil model dulu, apa yang terjadi bila barisnya tidak ada, dan bagaimana memastikan penghapusan benar-benar terjadi. Chapter ini menjawab ketiganya lewat delete(), destroy(), penghapusan massal, sampai truncate() yang mengosongkan tabel total. Kita kembali memakai tabel posts berisi tiga baris: dua post milik Budi dan satu post milik Siti.

Verifikasi Awal dengan count() dan exists() #

Sebelum menghapus apa pun, mari biasakan memeriksa keadaan tabel. Method count() mengembalikan jumlah baris, sedangkan exists() menjawab “apakah tabel ini punya setidaknya satu baris?” dalam satu query paling murah:

php
echo 'count(): ', Post::count(), "\n";
echo 'exists(): ', Post::exists() ? 'true' : 'false', "\n";

Output yang Dihasilkan:

text
count(): 3
exists(): true

Keduanya akan menjadi alat verifikasi standar kita sepanjang chapter ini — setelah setiap operasi hapus, kita cek ulang angkanya.

Menghapus Satu Record dengan delete() #

Jalur paling dasar: ambil model dengan find(), lalu panggil delete() pada instance-nya.

php
$post = Post::find(1);
echo 'Ditemukan: ', $post->judul, "\n";

$hasil = $post->delete();
echo 'delete() mengembalikan: ', var_export($hasil, true), "\n";
echo 'count() sekarang: ', Post::count(), "\n";
echo 'find(1) sekarang: ', var_export(Post::find(1), true), "\n";

Output yang Dihasilkan:

text
Ditemukan: Belajar Eloquent
delete() mengembalikan: true
count() sekarang: 2
find(1) sekarang: NULL

Method delete() mengembalikan true karena barisnya memang ada, jumlah baris turun dari tiga menjadi dua, dan find(1) kini mengembalikan NULL. Di balik layar, jalur ini mengirim satu pernyataan DELETE berdasarkan primary key model. Karena penghapusan terjadi pada instance model, event deleting/deleted tetap menyala sebelum dan sesudah baris hilang.

destroy() Tanpa Mengambil Model Dulu #

Mengambil model hanya untuk menghapusnya adalah satu query ekstra yang sia-sia. Method statis destroy() menerima primary key dan mengerjakan pengambilan sekaligus penghapusan:

php
$hasil = Post::destroy(2);
echo 'destroy(2) mengembalikan: ', var_export($hasil, true), "\n";
echo 'count() sekarang: ', Post::count(), "\n";

$hasil = Post::destroy(999);
echo 'destroy(999) mengembalikan: ', var_export($hasil, true), "\n";
echo 'find(999) mengembalikan: ', var_export(Post::find(999), true), "\n";

Output yang Dihasilkan:

text
destroy(2) mengembalikan: 1
destroy(999) mengembalikan: 0
find(999) mengembalikan: NULL

Perhatikan nilai kembaliannya: destroy() melaporkan jumlah baris yang benar-benar terhapus. Menghapus id 2 menghasilkan 1, sedangkan id 999 yang tidak ada dihasilkan 0 — bukan error. Method ini juga menerima array atau beberapa argumen sekaligus (Post::destroy([3, 4, 5]) atau Post::destroy(3, 4, 5)), dan setiap model tetap diambil dulu di balik layar sehingga event model tetap menyala — detail lengkapnya ada di dokumentasi Eloquent .

Penghapusan Massal pada Query #

Untuk menghapus banyak baris berdasarkan kondisi, panggil delete() langsung pada query:

php
$hasil = Post::where('views', '>', 0)->delete();
echo 'Baris terhapus: ', var_export($hasil, true), "\n";
echo 'count() sekarang: ', Post::count(), "\n";
echo 'exists(): ', Post::exists() ? 'true' : 'false', "\n";

$hasil = Post::where('views', '>', 0)->delete();
echo 'Kedua kali (tak ada cocok): ', var_export($hasil, true), "\n";

Output yang Dihasilkan:

text
Baris terhapus: 1
count() sekarang: 0
exists(): false
Kedua kali (tak ada cocok): 0

Panggilan pertama menghapus post terakhir milik Siti (satu-satunya baris dengan views > 0), mengosongkan tabel, dan exists() pun berubah menjadi false. Panggilan kedua dengan kondisi yang sama mengembalikan 0 — penghapusan massal tidak pernah error walau tidak ada yang cocok, jadi angka kembaliannya adalah satu-satunya petunjuk.

truncate() — Mengosongkan Total #

Method truncate() menghapus semua baris sekaligus mereset hitungan auto-increment. Kita isikan dua baris dulu, lalu kosongkan:

php
DB::table('posts')->insert([
    ['user_id' => 1, 'judul' => 'Post Sampah A', 'slug' => 'post-sampah-a',
     'isi' => 'x', 'views' => 0, 'published' => false,
     'created_at' => now(), 'updated_at' => now()],
    ['user_id' => 1, 'judul' => 'Post Sampah B', 'slug' => 'post-sampah-b',
     'isi' => 'x', 'views' => 0, 'published' => false,
     'created_at' => now(), 'updated_at' => now()],
]);

echo 'Sebelum truncate: ', Post::count(), " post\n";

Post::truncate();

echo 'Sesudah truncate: ', Post::count(), " post\n";

$idBaru = DB::table('posts')->insertGetId([
    'user_id' => 1, 'judul' => 'Post Baru', 'slug' => 'post-baru',
    'isi' => 'x', 'views' => 0, 'published' => false,
    'created_at' => now(), 'updated_at' => now(),
]);
echo 'Insert setelah truncate mendapat id: ', $idBaru, "\n";

Output yang Dihasilkan:

text
Sebelum truncate: 2 post
Sesudah truncate: 0 post
Insert setelah truncate mendapat id: 1

Tiga baris keluaran itu membuktikan tiga sifat sekaligus: tabel kosong setelah truncate(), baris baru mendapat id 1 karena hitungan auto-increment ikut direset (teruji di SQLite), dan tidak ada mekanisme pembatalan sama sekali. truncate() tidak menjalankan event model, tidak menghormati soft delete, dan dapat gagal di database yang masih menahan foreign key dari tabel lain — gunakan hanya untuk tabel uji atau data staging.

CRUD kini lengkap: kita bisa membuat, membaca, memperbarui, dan menghapus. Namun makin banyak query kita tulis, makin terlihat bahwa kondisi-kondisi yang sama terus berulang — dan Eloquent punya fitur untuk memberi kondisi tersebut sebuah nama. Chapter berikutnya membahas query scope.