LA

laravel-event-driven-architecture

Guides the implementation of decoupled Laravel systems using events, listeners, and subscribers. Ensures proper event discovery and dispatching.

Install

mkdir -p .claude/skills/laravel-event-driven-architecture && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/17460" && unzip -o skill.zip -d .claude/skills/laravel-event-driven-architecture && rm skill.zip

Installs to .claude/skills/laravel-event-driven-architecture

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Best practices for Laravel events and listeners including event discovery, queued listeners, subscribers, and model events for decoupled architecture.
150 charsno explicit “when” trigger
Intermediate

Key capabilities

  • Structure event and listener classes in Laravel.
  • Utilize automatic listener discovery.
  • Dispatch events using static methods or the event helper.
  • Implement queued listeners for asynchronous operations.
  • Ensure transaction safety with `ShouldQueueAfterCommit` or `$afterCommit`.
  • Organize multiple event handlers using event subscribers.

How it works

This skill provides best practices and code examples for implementing event-driven architecture in Laravel, covering event and listener class structures, dispatching, queuing, and transaction safety.

Inputs & outputs

You give it
Laravel application code
You get back
decoupled Laravel event-driven architecture

When to use laravel-event-driven-architecture

  • Setting up decoupled event listeners
  • Implementing queued order notifications
  • Organizing complex application workflows with events

About this skill

Laravel Event-Driven Architecture

Event Class Structure

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public readonly Order $order,
    ) {}
}

Listener Class Structure

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;

class SendOrderConfirmation
{
    public function handle(OrderPlaced $event): void
    {
        $event->order->user->notify(
            new OrderConfirmationNotification($event->order)
        );
    }
}

Automatic Listener Discovery

Laravel auto-discovers listeners when they are in the App\Listeners directory and have a handle method type-hinting an event. No manual registration needed.

// ✅ Auto-discovered - just create the class with a typed handle method
class SendOrderConfirmation
{
    public function handle(OrderPlaced $event): void { /* ... */ }
}

// ✅ One listener handling multiple events
class AuditLogger
{
    public function handleOrderPlaced(OrderPlaced $event): void { /* ... */ }
    public function handleOrderCancelled(OrderCancelled $event): void { /* ... */ }
}

// ❌ Won't be discovered - missing type hint
class SendOrderConfirmation
{
    public function handle($event): void { /* ... */ }
}

Dispatching Events

// ✅ Using static dispatch
OrderPlaced::dispatch($order);

// ✅ Using event helper
event(new OrderPlaced($order));

// ❌ Calling listeners directly instead of dispatching events
(new SendOrderConfirmation)->handle($order); // Tight coupling

Queued Listeners

use Illuminate\Contracts\Queue\ShouldQueue;

// ✅ Listener runs asynchronously on the queue
class GenerateInvoicePdf implements ShouldQueue
{
    public string $queue = 'invoices';
    public int $tries = 3;
    public array $backoff = [10, 60];

    public function handle(OrderPlaced $event): void
    {
        $pdf = PdfGenerator::fromOrder($event->order);
        Storage::put("invoices/{$event->order->id}.pdf", $pdf);
    }

    public function failed(OrderPlaced $event, \Throwable $exception): void
    {
        // Handle failure
    }

    // Conditionally handle
    public function shouldQueue(OrderPlaced $event): bool
    {
        return $event->order->total > 0;
    }
}

ShouldQueueAfterCommit

use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;

// ✅ Only dispatched to queue after the database transaction commits
class UpdateSearchIndex implements ShouldQueueAfterCommit
{
    public function handle(OrderPlaced $event): void
    {
        SearchIndex::update('orders', $event->order);
    }
}

ShouldDispatchAfterCommit for Transaction Safety

// ✅ Event only dispatches after the surrounding transaction commits
class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $afterCommit = true;

    public function __construct(
        public readonly Order $order,
    ) {}
}

// This prevents listeners from running on data that might be rolled back
DB::transaction(function () {
    $order = Order::create($data);
    OrderPlaced::dispatch($order); // Dispatched only after commit
});

Event Subscribers

<?php

namespace App\Listeners;

use Illuminate\Events\Dispatcher;

class OrderEventSubscriber
{
    public function handleOrderPlaced(OrderPlaced $event): void
    {
        // Log order creation
    }

    public function handleOrderShipped(OrderShipped $event): void
    {
        // Send shipping notification
    }

    public function handleOrderCancelled(OrderCancelled $event): void
    {
        // Process refund
    }

    public function subscribe(Dispatcher $events): array
    {
        return [
            OrderPlaced::class => 'handleOrderPlaced',
            OrderShipped::class => 'handleOrderShipped',
            OrderCancelled::class => 'handleOrderCancelled',
        ];
    }
}

// Register in EventServiceProvider
protected $subscribe = [
    OrderEventSubscriber::class,
];

Model Events and Observers

<?php

namespace App\Observers;

use App\Models\Order;

class OrderObserver
{
    public function creating(Order $order): void
    {
        $order->reference = Order::generateReference();
    }

    public function created(Order $order): void
    {
        OrderPlaced::dispatch($order);
    }

    public function updating(Order $order): void
    {
        if ($order->isDirty('status') && $order->status === 'cancelled') {
            $order->cancelled_at = now();
        }
    }

    public function deleted(Order $order): void
    {
        Storage::deleteDirectory("orders/{$order->id}");
    }
}

// Register via model attribute (Laravel 11+)
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy(OrderObserver::class)]
class Order extends Model
{
    // ...
}

When to Use Events vs Direct Calls

// ✅ USE EVENTS when:
// - Multiple side effects from one action
// - Side effects may change independently
// - Side effects can be async
class OrderService
{
    public function place(Order $order): void
    {
        $order->save();

        // Multiple listeners handle: email, invoice, inventory, analytics
        OrderPlaced::dispatch($order);
    }
}

// ✅ USE DIRECT CALLS when:
// - Core business logic that must succeed together
// - Single clear responsibility
// - Synchronous transactional requirement
class OrderService
{
    public function place(Order $order): void
    {
        DB::transaction(function () use ($order) {
            $order->save();
            $this->inventoryService->reserve($order); // Must succeed together
        });

        OrderPlaced::dispatch($order); // Side effects via events
    }
}

// ❌ Don't use events for core logic that must not fail silently
// ❌ Don't use events when you need the return value

Testing Events

use Illuminate\Support\Facades\Event;

// ✅ Assert events were dispatched
public function test_placing_order_fires_event(): void
{
    Event::fake();

    $order = Order::factory()->create();
    $this->orderService->place($order);

    Event::assertDispatched(OrderPlaced::class, function ($event) use ($order) {
        return $event->order->id === $order->id;
    });
}

// ✅ Assert event not dispatched
public function test_cancelled_order_does_not_fire_placed(): void
{
    Event::fake();

    $order = Order::factory()->cancelled()->create();
    $this->orderService->place($order);

    Event::assertNotDispatched(OrderPlaced::class);
}

// ✅ Fake only specific events, let others run normally
public function test_order_with_real_listeners(): void
{
    Event::fake([OrderShipped::class]);

    // OrderPlaced listeners will run, OrderShipped will be faked
}

// ✅ Test listener directly
public function test_send_confirmation_listener(): void
{
    Notification::fake();

    $event = new OrderPlaced(Order::factory()->create());
    (new SendOrderConfirmation)->handle($event);

    Notification::assertSentTo($event->order->user, OrderConfirmationNotification::class);
}

Checklist

  • Events are simple data-carrying objects (no business logic)
  • Listeners have a single responsibility each
  • Queued listeners used for slow operations (email, PDF, API calls)
  • ShouldQueueAfterCommit or $afterCommit used for transaction safety
  • Auto-discovery relied on instead of manual registration where possible
  • Observers used sparingly and only for model lifecycle hooks
  • Core business logic not hidden inside event listeners
  • Events tested with Event::fake and assertDispatched
  • Listeners tested in isolation with direct handle() calls
  • shouldQueue() used to conditionally skip queuing

When not to use it

  • Do not use events for core business logic that must not fail silently.
  • Do not use events when you need the return value.
  • Do not call listeners directly instead of dispatching events.

Limitations

  • The skill focuses on Laravel-specific event patterns.
  • It does not cover event-driven architecture outside the Laravel framework.
  • It does not provide guidance on when to use events for core business logic that requires return values.

How it compares

This skill offers specific Laravel-centric patterns for event-driven architecture, including automatic discovery and transaction-safe queuing, which differs from a generic event implementation by use framework features.

Compared to similar skills

laravel-event-driven-architecture side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
laravel-event-driven-architecture (this skill)020dNo flagsIntermediate
laravel-patterns03moReviewAdvanced
laravel-pdf113moReviewBeginner
laravel-architecture713dNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry