Lompat ke konten
dokumentasi.id
Commit 159d6ac
v1
Star1

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-17. 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.
Fig. 1: Alur save menuju pengisian kolom userstampEloquent memicu creating atau updating, observer memeriksa isClean lalu membaca auth guard, dan kolom terisi sebelum INSERT atau UPDATE dijalankan.

Event yang ditangani

EventSyarat masukKolom yang diisiAksi tambahan
creatingisClean(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 aktiftidak ada
updatingisClean(config('userstamps.updated_by_column'))updated_by_id dan updated_by_type bila morph aktiftidak ada
deleting$model->usingSoftDeletes() bernilai truedeleted_by_id, updated_by_id, dan _type bila morph aktifmemanggil saveWithoutEventDispatching
restoring$model->usingSoftDeletes() bernilai truedeleted_by_id diisi null, updated_by_id diisi user aktif, _type mengikuti aturan morphmemanggil 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-28. Pada updating hanya blok updated_by yang berjalan src/Observers/UserstampsObserver.php:30-40. 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-17. Opsi itu bernilai false pada config bawaan config/userstamps.php:18, 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-59. Pada restoring, deleted_by_id dikosongkan menjadi null sementara updated_by_id diisi user yang melakukan restore src/Observers/UserstampsObserver.php:61-78. Syarat usingSoftDeletes() membaca in_array(SoftDeletes::class, class_uses_recursive(static::class)) src/Concerns/HasUserstamps.php:64-67, 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.
Fig. 2: Mekanisme save tanpa dispatch eventDispatcher 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-89. Tanpa pelepasan itu, save() di dalam deleting akan memicu updating, dan penyimpanan dapat berulang tanpa henti.

⚠️ saveWithoutEventDispatching tidak memakai blok try/finally. Bila save() 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 dalam deleting atau restoring.

⚠️ deleting dan restoring tidak mengisi kolom userstamp ketika usingSoftDeletes() bernilai false. Pada hard delete, deleted_by_id tetap kosong dan updated_by_id tidak 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-107.

KondisiNilai idNilai type
auth()->check() bernilai falsenullnull
users_table_id_column_name sama dengan idauth()->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-98.

Saat morph aktif, type diisi get_class($user) sehingga kolom _type menyimpan nama kelas model user yang sedang login src/Observers/UserstampsObserver.php:103-106. 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:71. Pengaruh opsi ini terhadap kolom di database dijelaskan di Referensi Opsi Konfigurasi.