2.3
Observer dan Siklus Event Model
UserstampsObserver adalah satu-satunya komponen yang menulis nilai ke kolom userstamp. Observer ini tidak dipasang manual lewat $observers, melainkan didaftarkan oleh trait HasUserstamps di dalam hook bootHasUserstamps, yang menambahkan listener pada event booted lalu memanggil static::observe(UserstampsObserver::class) src/Concerns/HasUserstamps.php:12-171. Karena pendaftarannya menumpang event booted, setiap model yang memakai trait itu langsung mendapat observer tanpa konfigurasi tambahan. Cara trait ini membangun relasi ke model user dijelaskan di Trait HasUserstamps dan Relasi User.
Urutan panggilan dari save() sampai kolom terisi
Observer hanya membaca model yang sedang diproses Eloquent. Ia tidak pernah memanggil save() sendiri pada creating maupun updating, karena pada dua event itu nilai yang ia set hanya perlu ikut terbawa oleh query yang sedang dijalankan Eloquent.
sequenceDiagram
autonumber
participant Caller as Application code
participant Model as Eloquent model
participant Obs as UserstampsObserver
participant Guard as auth guard
participant DB as database
Caller->>Model: save
Model->>Obs: creating
Obs->>Model: isClean created_by_column
Model-->>Obs: clean or dirty
Obs->>Guard: check lalu user
Guard-->>Obs: id dan class
Obs->>Model: set created_by_id dan created_by_type
Obs->>Model: set updated_by_id dan updated_by_type
Model->>DB: INSERT
Caller->>Model: save berikutnya
Model->>Obs: updating
Obs->>Model: isClean updated_by_column
Obs->>Guard: check lalu user
Guard-->>Obs: id dan class
Obs->>Model: set updated_by_id dan updated_by_type
Model->>DB: UPDATE
%% fig: Alur save menuju pengisian kolom userstamp
%% desc: Eloquent memicu creating atau updating, observer memeriksa isClean lalu membaca auth guard, dan kolom terisi sebelum INSERT atau UPDATE dijalankan.Event yang ditangani
| Event | Syarat masuk | Kolom yang diisi | Aksi tambahan |
|---|---|---|---|
creating | isClean(config('userstamps.created_by_column')), lalu terpisah isClean(config('userstamps.updated_by_column')) | created_by_id, updated_by_id, dan masing-masing _type bila morph aktif | tidak ada |
updating | isClean(config('userstamps.updated_by_column')) | updated_by_id dan updated_by_type bila morph aktif | tidak ada |
deleting | $model->usingSoftDeletes() bernilai true | deleted_by_id, updated_by_id, dan _type bila morph aktif | memanggil saveWithoutEventDispatching |
restoring | $model->usingSoftDeletes() bernilai true | deleted_by_id diisi null, updated_by_id diisi user aktif, _type mengikuti aturan morph | memanggil saveWithoutEventDispatching |
Pada creating, kedua blok berjalan berurutan dan masing-masing memanggil getUserPrimaryValueAndClass() sendiri, sehingga created_by dan updated_by terisi dari user yang sama pada saat pembuatan record src/Observers/UserstampsObserver.php:9-282. Pada updating hanya blok updated_by yang berjalan src/Observers/UserstampsObserver.php:30-403. Nama kolom dasar diambil dari config created_by_column, updated_by_column, dan deleted_by_column; nilai bakunya adalah created_by, updated_by, dan deleted_by config/userstamps.php:84 config/userstamps.php:97 config/userstamps.php:110.
Pemeriksaan isClean berlaku sebagai penjaga: bila atribut sudah diubah sebelum save(), observer melewati blok tersebut dan nilai yang sudah ada tidak ditimpa. Karena setiap blok berdiri sendiri, menetapkan updated_by_id secara manual tidak menghalangi pengisian created_by_id pada operasi creating.
Penulisan _type bergantung pada config('userstamps.is_using_morph') di dalam setiap blok src/Observers/UserstampsObserver.php:15-177. Opsi itu bernilai false pada config bawaan config/userstamps.php:188, sehingga tanpa mengubahnya observer hanya menulis kolom _id. Perbedaan tipe kolom ID dan konsekuensinya dibahas di Mode Morph dan Tipe Kolom ID.
deleting dan restoring menyimpan sendiri
Dua event ini berbeda dari creating dan updating karena perubahan atribut terjadi setelah query penghapusan berjalan. Observer karena itu menyimpan model secara eksplisit. Pada deleting, deleted_by_id dan updated_by_id diisi user aktif lalu model disimpan src/Observers/UserstampsObserver.php:42-599. Pada restoring, deleted_by_id dikosongkan menjadi null sementara updated_by_id diisi user yang melakukan restore src/Observers/UserstampsObserver.php:61-7810. Syarat usingSoftDeletes() membaca in_array(SoftDeletes::class, class_uses_recursive(static::class)) src/Concerns/HasUserstamps.php:64-6711, jadi model yang memakai trait HasUserstamps tanpa trait SoftDeletes tidak mendapat perubahan apa pun di kedua event ini.
graph TD
A["deleting / restoring"] --> B["getEventDispatcher"]
B --> C["unsetEventDispatcher"]
C --> D["save"]
D --> E["setEventDispatcher"]
E --> F["return saved"]
style A fill:#f59e0b,color:#000
style B fill:#8b5cf6,color:#fff
style C fill:#f59e0b,color:#000
style D fill:#336791,color:#fff
style E fill:#2d7d46,color:#fff
style F fill:#2d7d46,color:#fff
%% fig: Mekanisme save tanpa dispatch event
%% desc: Dispatcher dilepas dari model sebelum save dan dipasang kembali sesudahnya, sehingga observer tidak terpanggil ulang saat menyimpan hasil deleting atau restoring.Inti mekanismenya ada di saveWithoutEventDispatching: dispatcher disimpan ke variabel lokal, unsetEventDispatcher() dilepas, save() dijalankan, lalu setEventDispatcher($eventDispatcher) memasangnya kembali dan nilai bool hasil save() dikembalikan src/Observers/UserstampsObserver.php:80-8912. Tanpa pelepasan itu, save() di dalam deleting akan memicu updating, dan penyimpanan dapat berulang tanpa henti.
⚠️
saveWithoutEventDispatchingtidak memakai bloktry/finally. Bilasave()melempar exception,setEventDispatcher()tidak pernah dijalankan dan dispatcher model tersebut tetap terlepas sampai instance itu tidak dipakai lagi. Periksa kembali bila ada kegagalan penyimpanan di dalamdeletingataurestoring.
⚠️
deletingdanrestoringtidak mengisi kolom userstamp ketikausingSoftDeletes()bernilaifalse. Pada hard delete,deleted_by_idtetap kosong danupdated_by_idtidak berubah, sehingga riwayat penghapusan tidak tercatat. Siklus lengkap delete dan restore dibahas di Siklus Soft Delete dan Restore.
Sumber nilai user
Semua nilai kolom berasal dari satu fungsi privat, getUserPrimaryValueAndClass, yang mengembalikan array berisi id dan type src/Observers/UserstampsObserver.php:91-10713.
| Kondisi | Nilai id | Nilai type |
|---|---|---|
auth()->check() bernilai false | null | null |
users_table_id_column_name sama dengan id | auth()->id() | get_class($user) |
users_table_id_column_name berbeda dari id | $user->{$idColumn} | get_class($user) |
Pembacaan user selalu lewat helper auth() di dalam observer, bukan lewat parameter atau constructor. Guard yang aktif pada saat event berjalan adalah guard yang dipakai, sehingga nilai userstamp mengikuti guard default aplikasi. Ketika belum ada user yang login, kedua kunci bernilai null dan kolom tetap ditulis dengan nilai null; observer tidak melewati penulisan kolom pada kondisi itu src/Observers/UserstampsObserver.php:93-9814.
Saat morph aktif, type diisi get_class($user) sehingga kolom _type menyimpan nama kelas model user yang sedang login src/Observers/UserstampsObserver.php:103-10615. Saat morph tidak aktif, type tetap dihitung tetapi tidak dipakai karena blok penulisan _type dijaga config. Opsi users_table_id_column_name yang menentukan cabang pengambilan id memiliki nilai bawaan id config/userstamps.php:7116. Pengaruh opsi ini terhadap kolom di database dijelaskan di Referensi Opsi Konfigurasi.