Eventos y Listeners
Los eventos permiten desacoplar partes de tu aplicación. En lugar de ejecutar código directamente cuando algo ocurre (un usuario se registra, se crea un pedido), disparas un evento y uno o más listeners reaccionan a él. Esto hace tu código más limpio, testeable y fácil de mantener.
¿Por qué usar eventos?
Imagina que cuando un usuario se registra quieres: enviar un email de bienvenida, notificar al equipo de ventas y registrar la acción en un log. Sin eventos, el controlador se llenaría de código:
<?php
// Sin eventos: controlador acoplado y difícil de mantener
public function register(Request $request): RedirectResponse
{
$user = User::create($validated);
// Enviar email de bienvenida
Mail::to($user)->send(new WelcomeMail($user));
// Notificar al equipo de ventas
Notification::send($salesTeam, new NewUserNotification($user));
// Registrar en el log
Log::info('Nuevo usuario registrado', ['user_id' => $user->id]);
// Y si mañana quieres añadir más acciones...
return redirect()->route('dashboard');
}
Con eventos, el controlador solo dispara un evento y cada listener se encarga de una tarea específica:
<?php
// Con eventos: controlador limpio y desacoplado
public function register(Request $request): RedirectResponse
{
$user = User::create($validated);
event(new UserRegistered($user));
return redirect()->route('dashboard');
}
Crear un evento
Un evento es una clase simple que contiene los datos relacionados con lo que ha ocurrido:
php artisan make:event UserRegistered
<?php
declare(strict_types=1);
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserRegistered
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public User $user
) {}
}
El evento simplemente almacena el usuario que se registró. Los traits que incluye son:
- Dispatchable: Permite disparar el evento con
UserRegistered::dispatch($user) - SerializesModels: Serializa correctamente los modelos Eloquent si el evento se encola
- InteractsWithSockets: Necesario si quieres transmitir el evento por WebSockets
Crear un listener
Un listener es una clase que reacciona a un evento y ejecuta una acción específica:
php artisan make:listener SendWelcomeEmail --event=UserRegistered
<?php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeMail($event->user));
}
}
El método handle recibe el evento como
parámetro y tiene acceso a todos sus datos (en este
caso, $event->user).
Descubrimiento automático de listeners
Laravel descubre automáticamente los listeners
gracias al type-hint del método handle.
Si el método recibe un evento como parámetro,
Laravel lo registra automáticamente:
<?php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail
{
// Laravel detecta automáticamente que este listener
// escucha UserRegistered gracias al type-hint
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeMail($event->user));
}
}
Laravel escanea el directorio app/Listeners
y registra automáticamente cada listener con su evento
correspondiente. No necesitas configuración adicional.
Importante: El descubrimiento
automático funciona por defecto en Laravel 11+.
Los listeners deben estar en app/Listeners
y el método handle debe tener el
evento como type-hint.
Registrar listeners manualmente
En la mayoría de casos no necesitas registrar nada:
el autodiscovery se encarga. Solo usa registro manual
si tus listeners están fuera de app/Listeners
o necesitas configuración especial:
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Events\UserRegistered;
use App\Listeners\LogUserRegistration;
use App\Listeners\NotifySalesTeam;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(UserRegistered::class, [
SendWelcomeEmail::class,
NotifySalesTeam::class,
LogUserRegistration::class,
]);
}
}
Puedes pasar un array de listeners para registrar varios a la vez. Se ejecutan en el orden del array.
Disparar eventos
Hay varias formas de disparar un evento:
<?php
use App\Events\UserRegistered;
// Usando el helper event()
event(new UserRegistered($user));
// Usando el método estático dispatch (gracias al trait Dispatchable)
UserRegistered::dispatch($user);
// Usando la facade Event
Event::dispatch(new UserRegistered($user));
La forma recomendada es UserRegistered::dispatch($user)
porque es más explícita y fácil de buscar en el código.
Listeners en cola
Si un listener realiza una tarea lenta (enviar email,
llamar a una API externa), puedes ejecutarlo en
segundo plano implementando ShouldQueue:
<?php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeMail($event->user));
}
}
Con solo implementar la interfaz ShouldQueue,
el listener se ejecutará en segundo plano. Laravel
serializará el evento y lo procesará cuando el worker
de colas esté disponible.
Importante: Para que los listeners encolados funcionen, necesitas tener un worker ejecutándose. Aprenderás más sobre esto en la siguiente lección sobre Colas y Jobs.
Eventos de modelos Eloquent
Eloquent dispara eventos automáticamente cuando ocurren acciones en los modelos:
creating/created: Antes/después de crearupdating/updated: Antes/después de actualizarsaving/saved: Antes/después de crear o actualizardeleting/deleted: Antes/después de eliminarrestoring/restored: Antes/después de restaurar (soft deletes)
Puedes escuchar estos eventos directamente en el modelo
usando el método booted:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Post extends Model
{
protected static function booted(): void
{
static::creating(function (Post $post) {
$post->slug = Str::slug($post->title);
});
static::deleting(function (Post $post) {
// Eliminar archivos asociados
$post->images()->each->delete();
});
}
}
Observers
Si tienes muchos eventos para un modelo, puedes agruparlos en un Observer:
php artisan make:observer PostObserver --model=Post
<?php
declare(strict_types=1);
namespace App\Observers;
use App\Models\Post;
use Illuminate\Support\Str;
class PostObserver
{
public function creating(Post $post): void
{
$post->slug = Str::slug($post->title);
$post->user_id = auth()->id();
}
public function updating(Post $post): void
{
if ($post->isDirty('title')) {
$post->slug = Str::slug($post->title);
}
}
public function deleting(Post $post): void
{
$post->comments()->delete();
}
}
Registra el observer usando el atributo ObservedBy
en el modelo:
<?php
declare(strict_types=1);
namespace App\Models;
use App\Observers\PostObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
#[ObservedBy(PostObserver::class)]
class Post extends Model
{
// ...
}
Detener la propagación
Si un listener devuelve false, se detiene
la ejecución de los siguientes listeners para ese evento:
<?php
class ValidateUserEmail
{
public function handle(UserRegistered $event): bool
{
if ($this->isDisposableEmail($event->user->email)) {
$event->user->delete();
return false; // Detiene la propagación
}
return true; // Continúa con los siguientes listeners
}
}
Resumen
- Los eventos son clases que representan algo que ha ocurrido
- Los listeners son clases que reaccionan a eventos y ejecutan acciones
- Laravel descubre listeners automáticamente por el type-hint del método
handle - Implementa
ShouldQueuepara ejecutar listeners en segundo plano - Eloquent dispara eventos automáticos (created, updated, deleted, etc.)
- Los Observers agrupan múltiples eventos de un modelo en una clase
- Usa el atributo
#[ObservedBy]para registrar observers en el modelo
Ejercicios
Ejercicio 1: Evento y listener básico
Crea un evento OrderPlaced que contenga
un pedido (Order) y un listener
SendOrderConfirmation que simule el
envío de un email de confirmación (usa Log::info
en lugar de enviar un email real).
Ver solución
php artisan make:event OrderPlaced
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
<?php
// app/Events/OrderPlaced.php
declare(strict_types=1);
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order
) {}
}
<?php
// app/Listeners/SendOrderConfirmation.php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Log;
class SendOrderConfirmation
{
// Laravel detecta automáticamente el evento por el type-hint
public function handle(OrderPlaced $event): void
{
Log::info('Enviando confirmación de pedido', [
'order_id' => $event->order->id,
'user_email' => $event->order->user->email,
'total' => $event->order->total,
]);
}
}
<?php
// Uso en el controlador
use App\Events\OrderPlaced;
public function store(Request $request): RedirectResponse
{
$order = Order::create([...]);
OrderPlaced::dispatch($order);
return redirect()->route('orders.show', $order);
}
Ejercicio 2: Observer para un modelo
Crea un observer para el modelo Article
que: genere automáticamente el slug a partir del
título cuando se crea, actualice el slug si el título
cambia, y registre en el log cuando un artículo
se elimina.
Ver solución
php artisan make:observer ArticleObserver --model=Article
<?php
// app/Observers/ArticleObserver.php
declare(strict_types=1);
namespace App\Observers;
use App\Models\Article;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class ArticleObserver
{
public function creating(Article $article): void
{
$article->slug = Str::slug($article->title);
}
public function updating(Article $article): void
{
if ($article->isDirty('title')) {
$article->slug = Str::slug($article->title);
}
}
public function deleted(Article $article): void
{
Log::info('Artículo eliminado', [
'id' => $article->id,
'title' => $article->title,
'deleted_by' => auth()->id(),
]);
}
}
<?php
// app/Models/Article.php
declare(strict_types=1);
namespace App\Models;
use App\Observers\ArticleObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
#[ObservedBy(ArticleObserver::class)]
class Article extends Model
{
protected $fillable = ['title', 'content', 'slug'];
}
Ejercicio 3: Múltiples listeners para un evento
Para el evento UserRegistered, crea
tres listeners: SendWelcomeEmail,
CreateDefaultSettings y
NotifyAdmins. Registra los tres en
AppServiceProvider.
Ver solución
php artisan make:listener SendWelcomeEmail --event=UserRegistered
php artisan make:listener CreateDefaultSettings --event=UserRegistered
php artisan make:listener NotifyAdmins --event=UserRegistered
<?php
// app/Listeners/SendWelcomeEmail.php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Log;
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
Log::info('Enviando email de bienvenida a ' . $event->user->email);
}
}
<?php
// app/Listeners/CreateDefaultSettings.php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
class CreateDefaultSettings
{
public function handle(UserRegistered $event): void
{
$event->user->settings()->create([
'notifications_enabled' => true,
'theme' => 'light',
'language' => 'es',
]);
}
}
<?php
// app/Listeners/NotifyAdmins.php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Models\User;
use Illuminate\Support\Facades\Log;
class NotifyAdmins
{
public function handle(UserRegistered $event): void
{
$adminCount = User::where('is_admin', true)->count();
Log::info("Notificando a {$adminCount} administradores sobre nuevo usuario");
}
}
<?php
// app/Providers/AppServiceProvider.php
declare(strict_types=1);
namespace App\Providers;
use App\Events\UserRegistered;
use App\Listeners\CreateDefaultSettings;
use App\Listeners\NotifyAdmins;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(UserRegistered::class, [
SendWelcomeEmail::class,
CreateDefaultSettings::class,
NotifyAdmins::class,
]);
}
}
¿Has encontrado un error o tienes una sugerencia para mejorar esta lección?
Escríbenos¿Quieres dominar Laravel avanzado?
Aprende arquitectura de software, patrones de diseño y técnicas avanzadas de Laravel en nuestros cursos premium con proyectos reales.
Ver cursos premium