Laravel Observer & Event — Panduan Praktis untuk Junior Dev
Tujuan artikel ini sederhana: bikin kamu paham apa itu Observer & Event di Laravel, kapan jangan dipakai, kapan justru tepat dipakai, plus alternatif yang lebih aman untuk tim yang tumbuh. Kita fokus ke Laravel 12.x.
1) Definisi Singkat (tanpa bunga-bunga)
- Model Events: “hook” yang ditembak Laravel saat model di-creating, created, updating, updated, deleting, dll. Bisa kamu map lewat properti
dispatchesEvents
atau pakai Observer (kelas yang ngumpulin handler event per model). (laravel.com) - Observer: Satu kelas berisi metode seperti
creating(User $u)
,updated(User $u)
, dst — otomatis dipanggil saat event model terjadi. Tujuannya “otomatisasi” perilaku model. (laravel.com) - Events & Listeners: Pola publish-subscribe di Laravel. Kamu dispatch event (mis.
UserRegistered
) lalu listener menanganinya. Event-nya sendiri bukan yang di-queue; yang bisa di-queue adalah listener (pakaiShouldQueue
). (laravel.com)
Intinya: Observer = event khusus Eloquent model. Event/Listener = event aplikasi yang lebih umum.
2) Sisi Gelap (yang sering bikin tim capek)
-
Efek samping tersembunyi
User::save()
kelihatan polos, tapi di balik layar bisa “mengirim email”, “sinkron ke Stripe”, “ubah invoice”, dll. Developer baru sulit menebak side effect-nya → beban kognitif naik. (Ini kritik utama Luke Kuzmish.) (cosmastech) -
Tes jadi rapuh & lambat Banyak test yang menyentuh
User::create()
mendadak ikut mengeksekusi observer/listener. Harus fake queue/event/notification di banyak tempat → tetesan kecil jadi banjir. (cosmastech) -
Operasi massal tidak memicu event
User::insert($rows)
atauUser::query()->update([...])
tidak menembakkan event model. Hubungan/relasi->update([...])
juga tidak. Kalau logika penting kamu taruh di observer, bulk ops bisa “lewat semua rambu”. (laravel.com) -
Ordering & reliabilitas (untuk Event/Listener) Listener yang di-queue dieksekusi oleh worker terpisah — urutan tidak dijamin. Kalau butuh urutan pasti, gunakan job chaining alih-alih berharap pada urutan listener. (laravel.com, laraveldaily.com)
-
Exception merusak request Listener sinkron yang lempar exception bisa menjatuhkan response. Pertanyaan: memang mau request gagal kalau email gagal kirim? Banyak kasus: tidak. (Dorong ke queue.) (laravel.com)
3) Prinsip Dasar Pemakaian
Jangan gunakan Observer & Event ketika:
- Logika bisnisnya inti & kompleks (mis. update user yang harus menyentuh banyak aggregate/layanan eksternal). Taruh di Action/Service yang eksplisit, bukan di hook tersembunyi. (cosmastech)
- Kamu butuh urutan terjamin antar langkah. Pakai Jobs + Bus::chain() / batches. (laravel.com, laraveldaily.com)
- Kamu sering melakukan bulk insert/update. Event model tidak akan tembak; pindahkan logika ke layer lain atau proses ulang secara eksplisit. (laravel.com)
- Tim besar, context switching tinggi, dan kamu ingin mengurangi beban kognitif & flaky tests. (cosmastech)
Gunakan Observer & Event saat:
- Kebutuhan ringan, lokal, dan deterministik pada model: set
uuid
dicreating
, auto-slug, touch timestamp, audit sederhana. (Murah dan idempotent.) (laravel.com)- Notifikasi/asinkron ringan yang boleh gagal sementara: kirim email sambutan setelah
UserRegistered
, catat aktivitas, kirim metric. Pastikan listener di-queue (ShouldQueue
). (laravel.com)- Plugin/ekstensi antar domain yang loose coupling (integrasi opsional).
- Broadcasting ke frontend (Live updates), di mana event memang media komunikasi (mis. Reverb/Pusher/Ably). (laravel.com)
4) Alternatif yang Lebih Aman (dan terukur)
a) Action / Command Pattern (direkomendasikan)
Kumpulkan orkestrasi bisnis di satu kelas eksplisit.
// app/Actions/UpdateUserAction.php
namespace App\Actions;
use App\Models\User;
use App\Jobs\SyncStripeCustomer;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Bus;
class UpdateUserAction
{
public function __invoke(User $user, array $data): User
{
return DB::transaction(function () use ($user, $data) {
$user->fill($data)->save();
// Kerjakan efek samping DI SINI, eksplisit & teruji:
Bus::chain([
new SyncStripeCustomer($user->id),
// job lain yang butuh urutan…
])->dispatch();
return $user->refresh();
});
}
}
Kenapa ini lebih baik buat tim? Terlihat jelas dari atas ke bawah; mudah dites unit; tidak menyergap test lain dengan side effect tersembunyi. (Ini inti saran di artikel Luke.) (cosmastech)
b) Jobs, Chaining, & Batches
- Pindahkan kerja berat/IO (email, API, ekspor) ke queued jobs.
- Butuh urutan?
Bus::chain([...])
. Butuh paralel + agregasi? batches. - Monitoring? Laravel Horizon. (laravel.com, laraveldaily.com)
c) Scheduling
Pekerjaan berkala → Task Scheduler (mis. re-sync nightly) daripada event. (laravel.com)
5) Best Practice (ceklist cepat)
Kebersihan Kode & DX
- Satu model => maksimal 1–2 cheap observer rule. Hindari logika berat.
- Nama event semantik domain (
UserRegistered
,InvoiceFinalized
) — bukan event teknis (UserSaved
). (laravel.com) - Dokumentasikan side effect di tempat pemanggilan (atau di Action), bukan di kepala orang.
Testing
- Default di test:
Event::fake()
,Notification::fake()
,Queue::fake()
; lalu assert yang memang diharapkan ter-dispatch. (laravel.com) - Untuk Factory: gunakan
createQuietly()
jika tujuan test bukan memicu observer. (Ingat: mungkin ada cabang logic yang ingin tetap diuji.) (laravel.com) - Uji Action secara langsung, bukan hanya efek samping via controller.
Kinerja & Konsistensi
- Jangan gantungkan integritas bisnis pada observer kalau kamu rutin memakai bulk ops. Event tidak akan menembak. (Solusi: proses eksplisit setelah bulk, atau action khusus.) (laravel.com)
- Listener berat wajib
ShouldQueue
. Simpan request lifecycle tetap cepat. (laravel.com)
Keandalan
- Urutan penting? Jangan andalkan urutan listener. Pakai chaining. (laraveldaily.com)
- Integrasi rapuh (API pihak ketiga)? Taruh di job dengan retry/backoff, bukan di observer sinkron. (laravel.com)
6) Contoh Minimal
Observer “ringan” (boleh)
// app/Observers/UserObserver.php
namespace App\Observers;
use App\Models\User;
use Illuminate\Support\Str;
class UserObserver
{
public function creating(User $user): void
{
$user->uuid ??= (string) Str::uuid(); // murah & idempotent
}
}
Daftarkan di AppServiceProvider
:
use App\Models\User;
use App\Observers\UserObserver;
public function boot(): void
{
User::observe(UserObserver::class);
}
Catatan: mass update/insert tidak memicu ini. (laravel.com)
Event/Listener untuk email sambutan
// App\Events\UserRegistered.php
class UserRegistered {
use Illuminate\Foundation\Events\Dispatchable;
public function __construct(public int $userId) {}
}
// App\Listeners\SendWelcomeEmail.php
class SendWelcomeEmail implements Illuminate\Contracts\Queue\ShouldQueue {
public function handle(UserRegistered $event) { /* Mail::to(...)->send(...) */ }
}
Listener di-queue, event-nya tidak. (laravel.com)
7) Pola Keputusan (versi kilat)
- Perlu urutan pasti atau transaksi? → Action + Job Chaining, tidak pakai listener. (laravel.com, laraveldaily.com)
- Efek samping ringan & lokal ke model? → Observer oke. (laravel.com)
- Efek samping asinkron non-kritis (notifikasi, log) → Event + queued listener. (laravel.com)
- Operasi massal? → Jangan andalkan observer/event model. Jalankan proses eksplisit setelahnya. (laravel.com)
8) Sumber Resmi & Rujukan
- Laravel 12.x Eloquent (Observers, Muting Events, dispatchesEvents) — referensi utama untuk perilaku event model & catatan mass update tidak memicu event. (laravel.com)
- Laravel 12.x Events — cara kerja listener & queuing. (laravel.com)
- Laravel 12.x Queues — jobs, retry, chaining/batching. (laravel.com)
- Laravel 12.x Scheduling — tugas berkala. (laravel.com)
- Laravel Horizon — monitoring queue. (laravel.com)
- Artikel: The Pitfalls of Events and Laravel Observers in Large Teams (Luke Kuzmish) — kritik tajam untuk skala tim besar. (cosmastech)
Penutup
Observer & Event bukan musuh, tapi bukan palu serbaguna. Untuk junior dev: mulai dari Action yang eksplisit, lempar kerja berat ke Jobs, pakai Observer/Event hanya untuk kasus ringan. Kamu akan dapat kode yang lebih bisa ditebak, tes yang lebih stabil, dan tim yang tidak terseret efek samping tak terlihat.
- Video About Observer and Event