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
<?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:

php
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:

bash
php artisan event:list --event=App\\Events\\OrderShipped

Output yang Dihasilkan:

text
   INFO  Your application doesn't have any events matching the given criteria.

Setelah Event::subscribe() aktif, peta langsung berubah:

text
  App\Events\OrderShipped ....................................................
  ⇂ App\Listeners\OrderActivitySubscriber@notifyOrderShipped

Satu 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:

php
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:

text
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.

AspekListener terpisahSubscriber
Cakupansatu event per classbanyak event per class
Discovery otomatisya (via type hint handle)tidak, wajib Event::subscribe
Ber-queueimplement ShouldQueue di classsama — per class, bukan per method
Cocok untukreaksi yang berdiri sendirireaksi 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.