Laravel’s event system implements the observer pattern. When something notable happens in your application—an order ships, a user registers—you dispatch an event. One or more listeners respond to that event independently.This decouples your core business logic from side effects like sending emails or updating analytics. You can add, remove, or change listeners without touching the code that raised the event.
By default, Laravel scans app/Listeners automatically and registers any listener method that begins with handle or __invoke. The event it listens to is determined by the type-hint in the method signature:
use App\Events\OrderShipped;class SendShipmentNotification{ /** * Handle the event. */ public function handle(OrderShipped $event): void { // ... }}
No manual registration needed. Cache the manifest before deploying to production:
php artisan event:cache# Clear the cachephp artisan event:clear
An event is a data container. It holds the information needed by listeners—nothing more:
<?phpnamespace App\Events;use App\Models\Order;use Illuminate\Broadcasting\InteractsWithSockets;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Queue\SerializesModels;class OrderShipped{ use Dispatchable, InteractsWithSockets, SerializesModels; /** * Create a new event instance. */ public function __construct( public Order $order, ) {}}
SerializesModels ensures Eloquent models serialize correctly when the event is used with a queued listener.
Listeners that send emails or call external APIs should run in the background. Implement ShouldQueue to push the listener onto the queue automatically:
<?phpnamespace App\Listeners;use App\Events\OrderShipped;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Queue\InteractsWithQueue;class SendShipmentNotification implements ShouldQueue{ use InteractsWithQueue; /** * The number of times the queued listener may be attempted. */ public int $tries = 3; /** * Handle the event. */ public function handle(OrderShipped $event): void { // Runs in a background queue worker... } /** * Handle a listener failure. */ public function failed(OrderShipped $event, \Throwable $exception): void { // Clean up or notify on failure... }}
Customize the queue connection and name:
public string $connection = 'redis';public string $queue = 'listeners';
Wrap a closure with queueable to run it on the queue:
use App\Events\OrderShipped;use function Illuminate\Events\queueable;use Illuminate\Support\Facades\Event;Event::listen(queueable(function (OrderShipped $event) { // Runs in the background...})->onQueue('notifications'));
<?phpnamespace App\Events;use App\Models\Order;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Queue\SerializesModels;class OrderShipped{ use Dispatchable, SerializesModels; public function __construct( public Order $order, ) {}}
Fake all events to assert they were dispatched without triggering listeners:
Pest
PHPUnit
use App\Events\OrderShipped;use Illuminate\Support\Facades\Event;test('order can be shipped', function () { Event::fake(); $order = Order::factory()->create(); $this->post("/orders/{$order->id}/ship"); Event::assertDispatched(OrderShipped::class, function ($event) use ($order) { return $event->order->id === $order->id; });});
use App\Events\OrderShipped;use Illuminate\Support\Facades\Event;public function test_order_can_be_shipped(): void{ Event::fake(); $order = Order::factory()->create(); $this->post("/orders/{$order->id}/ship"); Event::assertDispatched(OrderShipped::class, function ($event) use ($order) { return $event->order->id === $order->id; });}
Queues
Learn how to run queued listeners and background jobs at scale.