Lompat ke konten
dokumentasi.id
Commit 159d6ac
v1
Star1

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

6

Siklus Soft Delete dan Restore

Model yang memakai trait SoftDeletes mendapat dua tahap tambahan pada siklus hidup record: deleting mengisi deleted_by_id sekaligus updated_by_id, dan restoring mengosongkan deleted_by_id lalu mengisi ulang updated_by_id. Kedua branch hanya berjalan ketika usingSoftDeletes() bernilai true (src/Observers/UserstampsObserver.php:42-59, src/Observers/UserstampsObserver.php:61-78). Observer sendiri dipasang lewat event booted di trait, sehingga model uji seperti Product yang memakai HasUserstamps dan SoftDeletes langsung ikut (src/Concerns/HasUserstamps.php:12-17, tests/Models/Product.php:9-19).

Kapan branch soft delete dijalankan

Penjaga branch adalah usingSoftDeletes(), yang hanya memeriksa apakah trait SoftDeletes ada di rantai class_uses_recursive() milik model (src/Concerns/HasUserstamps.php:64-67). Artinya keputusan diambil dari sifat kelas, bukan dari jenis operasi yang sedang berjalan. Nama kolom yang dipakai bukan string tetap, melainkan hasil config('userstamps.deleted_by_column') dan config('userstamps.updated_by_column') yang ditambah akhiran _id atau _type (config/userstamps.php:110-120).

Transisi status dan kolom yang berubah

stateDiagram-v2
    [*] --> active: create
    active --> active: delete()
    active --> trashed: delete()
    trashed --> active: restore()

    note right of active
        deleted_by_id = null
        updated_by_id = user id
    end note

    note right of trashed
        deleted_by_id = user id
        updated_by_id = user id
    end note

    classDef ok fill:#2d7d46,color:#fff
    classDef store fill:#336791,color:#fff
    classDef danger fill:#dc2626,color:#fff
    class active ok
    class trashed store
%% fig: Siklus status record ber-Userstamps dengan SoftDeletes
%% desc: delete() memindahkan record ke status trashed sambil mengisi deleted_by_id, restore() mengembalikannya ke active dan mengosongkan kolom itu; updated_by_id selalu diisi user aktif.
Fig. 1: Siklus status record ber-Userstamps dengan SoftDeletesdelete() memindahkan record ke status trashed sambil mengisi deleted_by_id, restore() mengembalikannya ke active dan mengosongkan kolom itu; updated_by_id selalu diisi user aktif.

Cabang deleting menulis deleted_by_id (dan deleted_by_type bila is_using_morph aktif) tanpa syarat, lalu menimpa updated_by_id dengan user yang sama (src/Observers/UserstampsObserver.php:42-59). Cabang restoring melakukan kebalikannya: deleted_by_id dan deleted_by_type diset null, sedangkan updated_by_id tetap diisi user aktif (src/Observers/UserstampsObserver.php:61-78). Kolom deleted_at tidak disentuh observer sama sekali; kolom itu diurus oleh trait SoftDeletes milik Laravel.

OperasiEventdeleted_by_idupdated_by_idCatatan
delete() pada model ber-SoftDeletesdeletingdiisi user aktifdiisi user aktifnilai lama selalu ditimpa, tanpa pemeriksaan isClean()
restore()restoringdiset nulldiisi user aktifdeleted_by_type juga null saat morph aktif
save() biasaupdatingtidak disentuhdiisi hanya bila kolom masih cleankolom yang sudah diubah manual tidak ditimpa (src/Observers/UserstampsObserver.php:30-40)
tanpa user terautentikasideleting atau restoringnullnullgetUserPrimaryValueAndClass() mengembalikan id dan type bernilai null (src/Observers/UserstampsObserver.php:91-107)

Penyimpanan di dalam event

Kedua branch tidak mengandalkan save() milik pemanggil. Setelah atribut diisi, observer memanggil saveWithoutEventDispatching(): dispatcher event dilepas, $model->save() dijalankan, lalu dispatcher dipasang kembali (src/Observers/UserstampsObserver.php:80-89). Karena dispatcher sedang dilepas, event updating tidak terpicu dan branch updating tidak menimpa nilai yang baru saja ditulis. Efek sampingnya, pada deleting ada satu UPDATE stempel yang dijalankan sebelum Laravel menulis deleted_at.

⚠️ deleting dan restoring menimpa updated_by_id secara langsung, sedangkan branch updating memakai isClean() sebagai penjaga (src/Observers/UserstampsObserver.php:30-40, src/Observers/UserstampsObserver.php:42-59). Nilai updated_by_id yang sengaja diubah sebelum delete() akan hilang.

⚠️ Tanpa user terautentikasi, getUserPrimaryValueAndClass() mengembalikan ['id' => null, 'type' => null] (src/Observers/UserstampsObserver.php:91-107). delete() atau restore() dari console, queue, atau seeder karena itu mengosongkan deleted_by_id maupun updated_by_id pada record tersebut.

Kolom nullable dan relasi setelah restore

Kolom deleted_by_id didaftarkan oleh macro softUserstamps() melalui addUserIdColumn(), yang selalu memanggil ->nullable() pada setiap tipe ID; saat morph aktif ditambah kolom deleted_by_type bertipe string dan nullable (src/Database/Schema/Macros/UserstampsMacro.php:72-85). Nullable itu yang membuat restoring bisa mengosongkan kolom tanpa melanggar constraint.

Membaca kembali pelaku penghapusan memakai relasi deletedBy, yang berbentuk morphTo atau belongsTo sesuai is_using_morph, dan dibungkus withTrashed() bila userstamps.with_trashed bernilai true (src/Concerns/HasUserstamps.php:49-62). Detail bentuk relasinya dibahas di Trait HasUserstamps dan Relasi User.

Bukti di rangkaian test

testUserstampsBehavior() menjalankan tiga aktor berurutan pada satu Product: John membuat, Jane menghapus, Alice memulihkan (tests/Feature/UserstampsBehaviorTest.php:24-39).

AktorAksiEkspektasi
JohnProduct::create()createdBy dan updatedBy terisi John, deletedBy bernilai null
Jane$product->delete()deletedBy dan updatedBy terisi Jane
Alice$product->restore()deletedBy kembali null, updatedBy terisi Alice

Setelah restore, test mengubah userstamps.with_trashed menjadi true, menghapus user Alice, lalu memastikan updatedBy masih mengembalikan Alice (tests/Feature/UserstampsBehaviorTest.php:41-47, config/userstamps.php:110-120). Karena penghapusan user di situ berupa soft delete, baris user tetap ada dan relasi tetap dapat dibaca selama with_trashed aktif.