LaravelEloquentDatabaseMenengah4 mnt baca

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

php
// 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();
        });
    }
};
php
// 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

php
// 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 kembali

Pola 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:

php
// 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?