Soft Delete: Hapus Tanpa Menghapus
Tandai data sebagai terhapus tapi tetap bisa dikembalikan.
Kenapa "Hapus" Tidak Selalu Berarti Hilang?
User menghapus postingan lalu menyesal lima menit kemudian. Admin butuh jejak audit siapa menghapus apa. Regulasi mengharuskan data dipertahankan dalam jangka waktu tertentu. Soft delete menjawab semuanya: baris hanya ditandai terhapus lewat kolom deleted_at, tapi fisiknya tetap ada dan bisa dikembalikan kapan pun. Ini beda dengan delete biasa yang datanya lenyap permanen.
Setup: Migration dan Trait
// database/migrations/2026_10_08_000001_add_deleted_at_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes(); // menambah kolom deleted_at nullable
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
}Hanya dua langkah itu. Trait-nya yang mengubah perilaku delete() menjadi pengisian timestamp, plus memasang global scope sehingga semua query model otomatis mengecualikan baris yang terhapus.
Penggunaan Sehari-Hari
// app/Http/Controllers/PostController.php
$post->delete(); // soft: hanya mengisi deleted_at
Post::all(); // otomatis menyembunyikan yang terhapus
Post::withTrashed()->get(); // tampilkan semua, termasuk yang terhapus
Post::onlyTrashed()->get(); // hanya yang terhapus (untuk halaman "tong sampah")
$post->restore(); // kembalikan seperti semula
$post->forceDelete(); // hapus permanen, tidak bisa kembaliPola umumnya di aplikasi nyata: tombol hapus di UI memanggil delete() biasa, halaman admin "Sampah" memakai onlyTrashed() dengan tombol Pulihkan dan Hapus Permanen berdampingan.
Jebakan umum: lupa bahwa unique constraint di database tetap menghitung baris yang di-soft-delete. User menghapus akun lalu mendaftar lagi dengan email yang sama? INSERT gagal karena email itu masih "dipakai" oleh baris yang terhapus. Solusinya antara lain: cek duplikat saat restore, pakai composite unique (email + deleted_at) jika database mendukung, atau kosongkan email saat soft delete.
Catatan teknis: Global scope whereNull('deleted_at') hanya dipasang oleh Eloquent pada model yang memakai trait. Kalau kamu query lewat DB::table('posts') mentah, filter itu TIDAK otomatis dan baris terhapus ikut muncul. Hati-hati juga dengan relasi: $user->posts otomatis memfilter yang terhapus, kecuali kamu menambahkan withTrashed() di definisi relasinya. Terakhir, forceDelete() tidak bisa di-undo, jadi selalu pasang konfirmasi ganda di UI sebelum memakainya.
Soft Delete dan Relasi Anak
Secara default, me-soft-delete parent tidak menyentuh child-nya. Kalau post di-soft-delete tapi komentarnya tetap muncul di Comment::all(), datanya jadi inkonsisten. Solusinya adalah cascade manual lewat model event:
// app/Models/Post.php
protected static function booted(): void
{
static::deleting(function ($post) {
if ($post->isForceDeleting()) {
$post->comments()->forceDelete();
} else {
$post->comments()->delete(); // ikut di-soft-delete
}
});
static::restoring(function ($post) {
$post->comments()->withTrashed()->restore(); // ikut pulih
});
}isForceDeleting() membedakan delete() biasa dari forceDelete(). Pola ini menjaga konsistensi: arsipkan post maka komentarnya ikut terarsip, pulihkan post maka komentarnya ikut pulih. Pastikan model Comment juga memakai trait SoftDeletes agar rantainya bekerja.
Tantangan
Tong Sampah
Tambahkan SoftDeletes ke model Post + migration softDeletes. Hapus 1 post, buktikan Post::all() tidak menampilkannya tapi Post::withTrashed() iya. Lalu restore.
Kuis Bab
Uji pemahamanmu: Bab 5: Eloquent CRUD Mendalam
Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".
1.Apa beda Post::find(1) dan Post::findOrFail(1)?
2.Kenapa Post::all()->count() lebih lambat dari Post::count()?
3.Apa fungsi $fillable?
4.Apa yang terjadi saat $post->delete() pada model dengan SoftDeletes?
5.Hasil DB::table('users')->get() berupa apa?