Event di Laravel hanyalah class PHP biasa — tidak ada parent wajib, tidak ada interface yang harus diimplementasi. Yang membedakannya dari class lain hanyalah perannya: object ini berkeliling membawa fakta dan data konteksnya dari pengirim ke para pendengarnya. Pada chapter ini kita membuat event pertama kita lewat artisan, merancang payload-nya ala DTO, lalu mengirimkannya dengan dua gaya dispatch dan membuktikan isinya sampai di tujuan dengan pengamat sementara.

Membuat Event dengan Artisan #

Kerangka event dibuat dengan perintah make:event:

bash
php artisan make:event OrderPlaced

Output yang Dihasilkan:

text
   INFO  Event [app/Events/OrderPlaced.php] created successfully.

Laravel menaruhnya di app/Events/OrderPlaced.php dengan isi bawaan seperti berikut:

php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct()
    {
        //
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('channel-name'),
        ];
    }
}

Ada dua hal yang perlu kita pahami dari kerangka ini. Method broadcastOn() beserta import Channel dan ShouldBroadcast adalah bekal untuk fitur broadcasting real-time (misalnya lewat Laravel Reverb) — karena event kita murni sisi server, bagian itu boleh dibuang. Sedangkan dua trait yang tersisa justru inti kegunaannya: Dispatchable memberi method statis dispatch(), dan SerializesModels membuat event aman dibawa ke queue karena model Eloquent di dalamnya akan disimpan sebagai identifier ringkas, bukan object utuh.

Merancang Payload ala DTO #

Event yang baik menyerupai DTO (data transfer object): propertinya jelas, final, dan tidak berubah setelah dibuat. PHP 8 constructor promotion dengan public readonly mewujudkan itu sekaligus:

php
<?php

namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public readonly string $orderId,
        public readonly string $email,
        public readonly int $total,
    ) {}
}

Mari kita bedah baris demi baris. Property orderId, email, dan total dipromosikan langsung dari parameter constructor, jadi deklarasi property dan pengisian nilai terjadi dalam satu tempat. Kata kunci readonly menjamin listener tidak bisa mengubah isi event setelah dikirim — event memang fakta masa lalu, bukan data yang boleh diedit. Terakhir, SerializesModels kita pertahankan agar event ini siap dipakai listener ber-queue nanti.

Bagaimana dengan isi payload itu sendiri? Prinsipnya: bawa data yang dibutuhkan listener, bukan seluruh dunia. Untuk OrderPlaced, identitas pesanan dan email pembeli memadai; bila kelak listener butuh rincian item, listener bisa memuat ulang order dari database berdasarkan orderId. Payload yang kecil membuat event mudah dibaca di log, murah di-serialize, dan tidak mengikat listener pada struktur data raksasa yang berubah setiap saat.

Mengirim Event dengan Dua Gaya #

Trait Dispatchable memberi jalan pintas OrderPlaced::dispatch(). Laravel juga menyediakan helper global event() yang menerima instance event. Sebelum mendengarkan lewat listener sungguhan (chapter berikutnya), kita pasang dulu pengamat sementara berupa closure Event::listen() untuk membuktikan event benar-benar lewat:

php
use App\Events\OrderPlaced;
use App\Events\PaymentSucceeded;
use Illuminate\Support\Facades\Event;

Event::listen(OrderPlaced::class, function (OrderPlaced $event) {
    printf("OrderPlaced: orderId=%s email=%s total=%d\n",
        $event->orderId, $event->email, $event->total);
});
Event::listen(PaymentSucceeded::class, function () {
    echo "PaymentSucceeded: (tanpa payload)\n";
});

OrderPlaced::dispatch('INV-101', '[email protected]', 250000);
event(new OrderPlaced('INV-102', '[email protected]', 175000));
PaymentSucceeded::dispatch();

Output yang Dihasilkan:

text
OrderPlaced: orderId=INV-101 [email protected] total=250000
OrderPlaced: orderId=INV-102 [email protected] total=175000
PaymentSucceeded: (tanpa payload)

Perhatikan perbedaan dua gaya dispatch di atas. OrderPlaced::dispatch() dipanggil dengan argumen constructor ('INV-101', '[email protected]', 250000), sedangkan helper event() menerima instance yang sudah jadi — keduanya menghasilkan dispatch yang identik. Pemanggilan PaymentSucceeded::dispatch() tanpa argumen juga sah: seperti terlihat di output, event tanpa payload tetap terkirim dan tetap bisa didengarkan.

Konvensi Penamaan #

Event dinamai dengan past tense karena isinya fakta, dan penamaan yang disiplin membuat seluruh tim bisa menebak arti event tanpa membuka isinya. OrderPlaced berarti pesanan sudah dibuat; jika kelak pesanan itu dikirim, event-nya OrderShipped — bukan ShipOrder, karena itu bentuk perintah, bukan kejadian. Event juga sebaiknya spesifik pada satu momen bisnis: lebih baik punya OrderPlaced dan OrderShipped terpisah daripada satu OrderUpdated yang maksudnya kabur.

Event hanyalah kurir berisi fakta — yang membuatnya berguna adalah pihak yang menunggu dan bereaksi. Pada chapter berikutnya kita membuat listener pertama dengan make:listener, menyuntikkan event ke method handle(), dan menghubungkan keduanya.