Laravel Framework

Events & Listeners

15 min Lesson 17 of 45

Events & Listeners in Laravel

Laravel's event system provides a simple observer pattern implementation, allowing you to subscribe and listen for various events in your application. Events provide a great way to decouple various aspects of your application, making your code more maintainable and testable.

Event-Driven Architecture

Events allow you to build loosely coupled systems where different parts of your application can react to actions without directly depending on each other:

Benefits of Events:
  • Decoupling: Components don't need to know about each other
  • Maintainability: Easy to add/remove functionality without touching existing code
  • Testability: Each listener can be tested independently
  • Reusability: Multiple listeners can respond to the same event
  • Asynchronous Processing: Listeners can be queued for background execution

Creating Events

Generate an event class using Artisan:

php artisan make:event OrderShipped php artisan make:event UserRegistered php artisan make:event PaymentProcessed

Example event class:

<?php namespace 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; public $order; public function __construct(Order $order) { $this->order = $order; } }

Event with additional context:

<?php namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class UserRegistered implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $user; public $verificationUrl; public $source; public function __construct(User $user, string $verificationUrl, string $source = 'web') { $this->user = $user; $this->verificationUrl = $verificationUrl; $this->source = $source; } // Broadcast on these channels public function broadcastOn() { return new PrivateChannel('admin'); } // Customize broadcast data public function broadcastWith() { return [ 'user_id' => $this->user->id, 'user_name' => $this->user->name, 'registered_at' => $this->user->created_at->toISOString(), ]; } }

Creating Listeners

Generate listener classes using Artisan:

php artisan make:listener SendShipmentNotification --event=OrderShipped php artisan make:listener UpdateInventory --event=OrderShipped php artisan make:listener SendWelcomeEmail --event=UserRegistered

Example listener class:

<?php namespace App\Listeners; use App\Events\OrderShipped; use App\Notifications\ShipmentNotification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; // Number of times to attempt public $tries = 3; // Timeout in seconds public $timeout = 60; // Queue name public $queue = 'notifications'; public function __construct() { // } public function handle(OrderShipped $event) { $order = $event->order; // Send notification to customer $order->customer->notify( new ShipmentNotification($order) ); // Log the shipment \Log::info('Shipment notification sent', [ 'order_id' => $order->id, 'customer_id' => $order->customer_id, ]); } // Handle job failure public function failed(OrderShipped $event, $exception) { \Log::error('Failed to send shipment notification', [ 'order_id' => $event->order->id, 'error' => $exception->getMessage(), ]); } }

Listener with conditional logic:

<?php namespace App\Listeners; use App\Events\OrderShipped; class UpdateInventory { public function handle(OrderShipped $event) { $order = $event->order; foreach ($order->items as $item) { $product = $item->product; // Decrease inventory $product->decrement('stock', $item->quantity); // Check if low stock if ($product->stock < $product->low_stock_threshold) { event(new LowStockAlert($product)); } // Check if out of stock if ($product->stock <= 0) { $product->update(['is_available' => false]); event(new OutOfStockAlert($product)); } } } // Stop propagation if needed public function shouldQueue(OrderShipped $event) { return $event->order->status === 'shipped'; } }

Registering Events and Listeners

Register events and listeners in EventServiceProvider:

<?php namespace App\Providers; use App\Events\OrderShipped; use App\Events\UserRegistered; use App\Listeners\SendShipmentNotification; use App\Listeners\UpdateInventory; use App\Listeners\SendWelcomeEmail; use App\Listeners\CreateUserProfile; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { protected $listen = [ // Order events OrderShipped::class => [ SendShipmentNotification::class, UpdateInventory::class, ], // User events UserRegistered::class => [ SendWelcomeEmail::class, CreateUserProfile::class, ], // Laravel built-in events 'Illuminate\Auth\Events\Login' => [ 'App\Listeners\LogSuccessfulLogin', ], ]; public function boot() { // } }
Tip: After adding event-listener mappings, run php artisan event:list to see all registered events and their listeners.

Dispatching Events

Dispatch events using multiple methods:

use App\Events\OrderShipped; // Using event() helper event(new OrderShipped($order)); // Using Event facade Event::dispatch(new OrderShipped($order)); // Using Dispatchable trait OrderShipped::dispatch($order); // Dispatch if condition is true OrderShipped::dispatchIf($order->status === 'shipped', $order); // Dispatch unless condition is true OrderShipped::dispatchUnless($order->status === 'cancelled', $order);

Dispatch events from models using model events:

<?php namespace App\Models; use App\Events\OrderShipped; use Illuminate\Database\Eloquent\Model; class Order extends Model { protected $dispatchesEvents = [ 'created' => OrderCreated::class, 'updated' => OrderUpdated::class, 'deleted' => OrderDeleted::class, ]; // Or use boot method protected static function boot() { parent::boot(); static::updated(function ($order) { if ($order->isDirty('status') && $order->status === 'shipped') { event(new OrderShipped($order)); } }); } }

Event Subscribers

Event subscribers allow you to subscribe to multiple events within a single class:

php artisan make:listener UserEventSubscriber

Subscriber class example:

<?php namespace App\Listeners; use App\Events\UserRegistered; use App\Events\UserLoggedIn; use App\Events\UserPasswordChanged; use Illuminate\Events\Dispatcher; class UserEventSubscriber { public function handleUserRegistration($event) { // Send welcome email $event->user->sendWelcomeEmail(); // Create default preferences $event->user->preferences()->create([ 'theme' => 'light', 'notifications_enabled' => true, ]); } public function handleUserLogin($event) { // Update last login timestamp $event->user->update([ 'last_login_at' => now(), 'last_login_ip' => request()->ip(), ]); // Log the activity activity('user-login') ->by($event->user) ->log('User logged in'); } public function handlePasswordChange($event) { // Send security notification $event->user->notify(new PasswordChangedNotification()); // Invalidate other sessions $event->user->invalidateOtherSessions(); } public function subscribe(Dispatcher $events) { $events->listen( UserRegistered::class, [UserEventSubscriber::class, 'handleUserRegistration'] ); $events->listen( UserLoggedIn::class, [UserEventSubscriber::class, 'handleUserLogin'] ); $events->listen( UserPasswordChanged::class, [UserEventSubscriber::class, 'handlePasswordChange'] ); } }

Register the subscriber in EventServiceProvider:

protected $subscribe = [ UserEventSubscriber::class, OrderEventSubscriber::class, ];

Queued Event Listeners

Make listeners queueable for better performance:

<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; public function handle(OrderShipped $event) { // This will run in the background $event->order->customer->notify( new ShipmentNotification($event->order) ); } // Manually determine if should queue public function shouldQueue(OrderShipped $event) { return $event->order->customer->prefers_email; } // Specify queue connection public function viaConnection() { return 'redis'; } // Specify queue name public function viaQueue() { return 'notifications'; } }
Warning: When using queued listeners, make sure all data passed in the event is serializable. Avoid passing closures or resources (like database connections) in events.

Stopping Event Propagation

Stop event propagation by returning false from a listener:

public function handle(OrderShipped $event) { // Do something if ($event->order->is_express_shipping) { // Stop other listeners from executing return false; } }
Exercise 1: Create a "ProductViewed" event and listener system. The event should track product views, update view counts, and recommend related products. Create at least 3 listeners for different concerns.
Exercise 2: Build an event subscriber for blog post events (created, updated, published, deleted). Each event should trigger appropriate actions like cache clearing, notification sending, and activity logging.
Exercise 3: Create a real-time notification system using broadcasted events. When a user receives a message, dispatch an event that broadcasts to their private channel and updates the UI in real-time.

Testing Events

Test events and listeners using Laravel's testing utilities:

use Illuminate\Support\Facades\Event; use App\Events\OrderShipped; public function test_order_shipped_event_is_dispatched() { Event::fake(); $order = Order::factory()->create(['status' => 'processing']); $order->update(['status' => 'shipped']); Event::assertDispatched(OrderShipped::class, function ($event) use ($order) { return $event->order->id === $order->id; }); } public function test_listeners_are_called() { Event::fake([OrderShipped::class]); event(new OrderShipped($order)); Event::assertListening( OrderShipped::class, SendShipmentNotification::class ); }

Events and listeners provide a powerful way to build decoupled, maintainable applications. In the next lesson, we'll explore queues and job processing for handling time-consuming tasks in the background.