Chapter sebelumnya berakhir dengan kejutan: listener yang belum kita daftarkan ternyata sudah aktif. Itu bukan kebetulan, melainkan event discovery — fitur yang aktif secara bawaan pada Laravel 13 dan membuat hampir semua registrasi manual menjadi tidak perlu. Pada chapter ini kita bedah cara kerjanya, membuktikan hasilnya lewat event:list, lalu memetakan kapan discovery unggul dan kapan registrasi eksplisit tetap lebih tepat.
Cara Kerja Auto-Discovery #
Saat aplikasi di-boot, framework memindai direktori app/Listeners dan membaca tiap class di dalamnya lewat reflection. Sebuah method terdaftar sebagai listener bila memenuhi dua syarat sekaligus: namanya diawali handle (atau tepatnya bernama __invoke), dan parameter pertamanya bertipe sebuah class — type hint itulah yang dianggap sebagai event yang didengarkan. Dari method handle(OrderPlaced $event) pada class SendOrderConfirmation, Laravel menyimpulkan pemetaan OrderPlaced → SendOrderConfirmation@handle tanpa perlu satu baris konfigurasi.
Perintah event:list memperlihatkan hasil pemindaian ini. Opsi --event memfilter keluarannya agar fokus pada satu event:
php artisan event:list --event=App\\Events\\OrderPlacedOutput yang Dihasilkan:
App\Events\OrderPlaced .....................................................
⇂ App\Listeners\OrderActivitySubscriber@recordOrderPlaced
⇂ App\Listeners\RecordOrderAnalytics@handle
⇂ App\Listeners\SendOrderConfirmation@handle
⇂ App\Listeners\SendOrderReport@handle (ShouldQueue)Empat listener menempel pada OrderPlaced. Sesuai peringatan chapter sebelumnya, baris Event::listen manual kita sudah hapus — SendOrderConfirmation dan RecordOrderAnalytics kini muncul murni dari discovery. Baris (ShouldQueue) menandai listener ber-queue yang akan kita bahas pada chapter Queued Listeners
, sedangkan entri OrderActivitySubscriber muncul dari registrasi Event::subscribe di AppServiceProvider (kita bahas tuntas pada chapter Event Subscribers
).
Apa yang Tidak Bisa Di-discover #
Discovery hanya membaca class di app/Listeners, sehingga tiga bentuk listener lain tetap butuh registrasi manual. Closure seperti yang kita pakai di chapter sebelumnya tidak punya berkas yang bisa dipindai, jadi Event::listen() tetap satu-satunya jalannya. Subscriber class juga tidak di-discover — mari kita buktikan dengan menonaktifkan sementara baris Event::subscribe() di AppServiceProvider:
php artisan event:list --event=App\\Events\\OrderShippedOutput yang Dihasilkan:
INFO Your application doesn't have any events matching the given criteria.Padahal OrderActivitySubscriber punya method notifyOrderShipped(OrderShipped $event) — tetapi nama methodnya tidak berawalan handle, sehingga discovery melewatinya. Setelah Event::subscribe(OrderActivitySubscriber::class) dikembalikan, listener itu muncul kembali di event:list. Bentuk ketiga adalah listener untuk event model Eloquent berbasis string seperti eloquent.updated: App\Models\Post — kita kerjakan di chapter Mendengarkan Model Events
.
Memilih Ikut atau Keluar dari Discovery #
Kadang sebuah listener hanya relevan di lingkungan tertentu — misalnya audit ringan yang hanya perlu jalan saat pengembangan. Laravel 13 menyediakan interface ShouldBeDiscovered agar class bisa memutuskan sendiri ikut tidaknya dipindai:
use Illuminate\Contracts\Events\ShouldBeDiscovered;
class DevOnlyAudit implements ShouldBeDiscovered
{
public static function shouldBeDiscovered(): bool
{
return app()->environment('local');
}
public function handle(OrderCanceled $event): void
{
Log::info('audit-dev', ['orderId' => $event->orderId]);
}
}Method statis shouldBeDiscovered() menampung kondisinya. Bukti nyatanya terlihat dari event:list untuk event OrderCanceled di dua lingkungan:
APP_ENV=production php artisan event:list --event=App\\Events\\OrderCanceledOutput yang Dihasilkan (lingkungan local terlebih dahulu, lalu production):
App\Events\OrderCanceled ...................................................
⇂ App\Listeners\OrderActivitySubscriber@logOrderCancellation
⇂ App\Listeners\DevOnlyAudit@handle
App\Events\OrderCanceled ...................................................
⇂ App\Listeners\OrderActivitySubscriber@logOrderCancellationDi local, DevOnlyAudit ikut terdaftar; di production listener itu hilang dari peta — tanpa menyentuh AppServiceProvider.
Discovery di Production dan di Test #
Pemindaian reflection berjalan setiap request, jadi di production kita cache hasilnya:
php artisan event:cacheOutput yang Dihasilkan:
INFO Events cached successfully.Perintah ini menulis hasil discovery ke bootstrap/cache/events.php sehingga framework berhenti memindai direktori. Setiap ada perubahan listener selama deployment, jalankan event:cache ulang; event:clear menghapus cache-nya. Di sisi lain, environment testing tidak punya tombol mematikan discovery — listener yang di-discover tetap aktif di dalam test, sehingga pengujian listener berjalan seperti di production. Chapter Pengujian Events
menunjukkan cara mengendalikannya dari dalam test lewat Event::fake().
Discovery vs Registrasi Manual #
| Aspek | Event discovery | Registrasi manual |
| — | — |
| Kode konfigurasi | nol baris | satu Event::listen per listener |
| Listener closure | tidak didukung | didukung |
| Listener ber-syarat (env, fitur) | lewat ShouldBeDiscovered | lewat kondisi PHP biasa |
| Keterlihatan alur | perlu event:list | eksplisit di provider |
| Risiko jebakan | dobel registrasi bila juga manual | lupa mendaftarkan listener baru |
Kedua jalur sama kuatnya, dan event:list adalah teman terbaik di keduanya: perintah ini menampilkan peta seluruh event beserta asal-usul listenernya, termasuk closure yang terdaftar oleh framework sendiri (misalnya Closure at: .../FoundationServiceProvider.php). Dengan peta yang rapi ini, kita siap membahas topik berikutnya: listener yang lambat tidak harus menyandera request — cukup dorong pekerjaannya ke antrean.