Events & Listeners
Decouple with events
Events let you react to things without cluttering your controllers.
Event
// app/Events/OrderPlaced.php
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order) {}
}
Controller
// app/Http/Controllers/OrderController.php
public function store(StoreOrderRequest $request)
{
$order = Order::create($request->validated());
OrderPlaced::dispatch($order);
// ...
}
Listener
// app/Listeners/UpdateOrderStatus.php
class UpdateOrderStatus
{
public function handle(OrderPlaced $event): void
{
if ($event->order->total <= 0) {
$event->order->update(['status' => 'cancelled']);
return;
}
$event->order->update(['status' => 'processing']);
}
}
Feature Test — assert event is dispatched
// tests/Feature/Http/Controllers/OrderController/StoreTest.php
Event::fake();
$this->post(route('orders.store'), $data);
Event::assertDispatched(OrderPlaced::class);
Unit Test — assert listener logic
// tests/Unit/Listeners/UpdateOrderStatusTest.php
class UpdateOrderStatusTest extends TestCase
{
#[Test]
public function listener_is_triggered_when_event_is_dispatched(): void
{
Event::assertListening(
OrderPlaced::class,
UpdateOrderStatus::class
);
}
#[Test]
public function status_is_processing_when_order_has_total(): void
{
$order = Order::factory()->create(['total' => 100]);
$event = new OrderPlaced($order);
$listener = new UpdateOrderStatus;
$listener->handle($event);
$this->assertSame('processing', $order->fresh()->status);
}
#[Test]
public function status_is_cancelled_when_order_is_free(): void
{
$order = Order::factory()->create(['total' => 0]);
$event = new OrderPlaced($order);
$listener = new UpdateOrderStatus;
$listener->handle($event);
$this->assertSame('cancelled', $order->fresh()->status);
}
}