Bayangkan tim audit yang ingin mencatat seluruh perjalanan sebuah pesanan: dibuat, dikirim, lalu dibatalkan. Kebutuhan itu bisa dipenuhi tiga listener terpisah — tetapi tiga berkas kecil yang saling berkerabat tersebar di app/Listeners membuat logika satu domain terpecah-pecah. Event subscriber menawarkan bentuk ketiga: satu class yang mendengarkan banyak event sekaligus, dengan satu method reaksi per event. Pada chapter ini kita membangun subscriber audit pesanan, mendaftarkannya, lalu membuktikan ketiga reaksinya bekerja.
Anatomi Subscriber #
Subscriber adalah class biasa dengan satu method istimewa bernama subscribe(). Method ini mengembalikan array pemetaan event => nama method:
<?php
namespace App\Listeners;
use App\Events\OrderCanceled;
use App\Events\OrderPlaced;
use App\Events\OrderShipped;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Facades\Log;
class OrderActivitySubscriber
{
public function recordOrderPlaced(OrderPlaced $event): void
{
Log::info('audit: order-dibuat', [
'orderId' => $event->orderId,
'total' => $event->total,
]);
}
public function notifyOrderShipped(OrderShipped $event): void
{
Log::info('audit: order-dikirim', [
'orderId' => $event->orderId,
'courier' => $event->courier,
]);
}
public function logOrderCancellation(OrderCanceled $event): void
{
Log::warning('audit: order-dibatalkan', [
'orderId' => $event->orderId,
'reason' => $event->reason,
]);
}
public function subscribe(Dispatcher $events): array
{
return [
OrderPlaced::class => 'recordOrderPlaced',
OrderShipped::class => 'notifyOrderShipped',
OrderCanceled::class => 'logOrderCancellation',
];
}
}Baca dari bawah ke atas: subscribe() memetakan tiap event ke nama method penangannya, dan masing-masing method menerima event lewat type hint persis seperti listener biasa. Tidak ada aturan nama untuk method reaksi — kita bebas memilih nama yang bermakna, seperti recordOrderPlaced di atas. Bebasnya penamaan ini juga alasan subscriber tidak bisa di-discovery otomatis (yang dicari discovery adalah method berawalan handle), sehingga ia menuntut satu baris registrasi.
Registrasi Subscriber #
Satu-satunya tempat pendaftaran adalah method boot() di AppServiceProvider — satu baris untuk seluruh kelompok:
use App\Listeners\OrderActivitySubscriber;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::subscribe(OrderActivitySubscriber::class);
}Sebelum baris itu ditulis, event:list membuktikan subscriber memang tidak dikenal:
php artisan event:list --event=App\\Events\\OrderShippedOutput yang Dihasilkan:
INFO Your application doesn't have any events matching the given criteria.Setelah Event::subscribe() aktif, peta langsung berubah:
App\Events\OrderShipped ....................................................
⇂ App\Listeners\OrderActivitySubscriber@notifyOrderShippedSatu class kini tercatat sebagai pendengar resmi tiga event sekaligus. Laravel juga menerima bentuk subscribe() yang memanggil $events->listen(...) satu per satu alih-alih mengembalikan array — hasilnya sama, hanya lebih verbose.
Menguji Ketiga Reaksi #
Bukti akhirnya adalah mengirim ketiga event dan membaca log yang tercatat:
use App\Events\OrderCanceled;
use App\Events\OrderPlaced;
use App\Events\OrderShipped;
OrderPlaced::dispatch('INV-901', '[email protected]', 480000);
OrderShipped::dispatch('INV-901', 'JNE Express');
OrderCanceled::dispatch('INV-901', 'pembeli minta refund');Output yang Dihasilkan:
local.INFO: audit: order-dibuat {"orderId":"INV-901","total":480000}
local.INFO: audit: order-dikirim {"orderId":"INV-901","courier":"JNE Express"}
local.WARNING: audit: order-dibatalkan {"orderId":"INV-901","reason":"pembeli minta refund"}Ketiga method reaksi terpanggil sesuai pemetaan, masing-masing menerima payload event-nya: total pesanan dari OrderPlaced, nama kurir dari OrderShipped, dan alasan pembatalan dari OrderCanceled. Perhatikan pula bahwa level log ikut bervariasi — pembatalan pesanan dicatat sebagai warning karena bernilai perhatian khusus, kebebasan yang tetap kita miliki karena tiap method adalah PHP biasa.
Kapan Subscriber Mengalahkan Listener #
Pilihan antara listener terpisah dan subscriber adalah soal pengelompokan, bukan kemampuan — keduanya mendukung queue dengan mekanisme yang sama. Gambarannya: listener terpisah seperti spesialis satu tugas yang direkrut per kejadian, sedangkan subscriber seperti satu tim kecil yang menangani seluruh alur kerja domain tertentu. Tim kecil itu pantas dibentuk ketika reaksi-reaksinya berbagi konteks yang sama — misalnya seluruhnya menulis ke jejak audit, atau seluruhnya menyentuh satu integrasi pihak ketiga — karena membaca satu class sudah cukup untuk memahami keseluruhan perilaku domain itu.
| Aspek | Listener terpisah | Subscriber |
|---|---|---|
| Cakupan | satu event per class | banyak event per class |
| Discovery otomatis | ya (via type hint handle) | tidak, wajib Event::subscribe |
| Ber-queue | implement ShouldQueue di class | sama — per class, bukan per method |
| Cocok untuk | reaksi yang berdiri sendiri | reaksi satu domain yang sekerabat |
Panduan praktisnya: gunakan listener terpisah ketika tiap reaksi punya alasan hidup sendiri (kirim email, hitung analytics); gunakan subscriber ketika beberapa reaksi berbagi tujuan dan konteks yang sama (audit, logging lintas tahap). Untuk subscriber ber-queue, seluruh class-nya menjadi job dengan sekali tambah implements ShouldQueue — karena berlaku per class, pastikan semua method di dalamnya aman dijalankan lewat antrean.
Satu catatan penutup tentang perkakas: make:listener --event=OrderShipped --queued menghasilkan kerangka listener yang sudah implements ShouldQueue sekaligus use InteractsWithQueue — praktis untuk listener tunggal, namun untuk subscriber kita tetap menulis class-nya manual karena make:listener tidak menghasilkan subscriber.
Ada satu keluarga event yang tidak kita buat sendiri namun menyala terus-menerus di aplikasi ber-Eloquent: event siklus hidup model. Chapter berikutnya menghubungkan dunia model tersebut dengan sistem event yang baru kita kuasai.