Qué son los eventos
El sistema de eventos de Laravel es una implementación sencilla del patrón observer.
Al despachar los sucesos (eventos) que ocurren en la aplicación y definir listeners que reaccionan a ellos, se reduce al mínimo la dependencia entre componentes.
Por ejemplo, al despachar el evento «pedido confirmado» pueden ejecutarse de forma independiente varios listeners: «enviar correo de confirmación», «descontar stock», «notificar a Slack», etc.
El código de procesamiento del pedido no necesita saber absolutamente nada de la implementación del envío del correo ni de la notificación a Slack.
Las clases de evento se ubican en app/Events y las de listener en app/Listeners.
Si esos directorios no existen, los comandos Artisan los crean automáticamente.
Generar eventos y listeners
Utiliza los comandos Artisan make:event y make:listener para generar el esqueleto.
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
Sin argumentos, los comandos te preguntan de forma interactiva.
php artisan make:event
php artisan make:listener
Registro de eventos
Descubrimiento automático de eventos
Por defecto, Laravel escanea el directorio app/Listeners y registra los listeners automáticamente.
Deduce la asociación con el evento inspeccionando el tipo del argumento del método handle o __invoke.
use App\Events\UserRegistered;
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
// Enviar el correo de bienvenida
}
}
Usando tipos union de PHP puedes escuchar varios eventos en el mismo método.
public function handle(UserRegistered|UserUpdated $event): void
{
// ...
}
Si tus listeners están en otro directorio, indícalo en bootstrap/app.php.
->withEvents(discover: [
__DIR__.'/../app/Domain/Orders/Listeners',
])
Puedes usar comodines para incluir varios directorios de una vez.
->withEvents(discover: [
__DIR__.'/../app/Domain/*/Listeners',
])
Puedes ver la lista de listeners registrados con este comando:
En producción, cachear el manifiesto de listeners mejora el rendimiento.
Al desplegar ejecuta php artisan optimize o php artisan event:cache.
Para borrar la caché usa php artisan event:clear.
Registro manual
También puedes registrar los listeners manualmente en el método boot de AppServiceProvider con el facade Event.
use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(
UserRegistered::class,
SendWelcomeEmail::class,
);
}
También puedes registrarlos como closure.
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(function (UserRegistered $event) {
// ...
});
}
Definir el evento
Las clases de evento son contenedores de datos. No contienen lógica y guardan como propiedades la información asociada al evento.
<?php
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 trait SerializesModels garantiza que los modelos Eloquent se manejen correctamente cuando los listeners encolados serialicen el evento.
Despachar eventos
Despacha eventos con el método estático dispatch o con el helper event().
use App\Events\UserRegistered;
// Método estático
UserRegistered::dispatch($user);
// Función auxiliar
event(new UserRegistered($user));
También hay métodos para despachar de forma condicional.
UserRegistered::dispatchIf($condition, $user);
UserRegistered::dispatchUnless($condition, $user);
Despachar tras una transacción de base de datos
Cuando quieras despachar el evento solo después de que la transacción se haya confirmado, implementa la interfaz ShouldDispatchAfterCommit en la clase del evento.
Si la transacción falla, el evento se descarta.
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserRegistered implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public User $user,
) {}
}
Implementar el listener
El listener recibe el evento en su método handle.
En el constructor, el contenedor de servicios inyecta automáticamente las dependencias.
<?php
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail
{
public function __construct() {}
public function handle(UserRegistered $event): void
{
Mail::to($event->user->email)
->send(new WelcomeMail($event->user));
}
}
Si el método handle devuelve false, se detiene la propagación del evento a los siguientes listeners.
Listeners encolados
Los procesos lentos, como el envío de correos o las peticiones HTTP, pueden ejecutarse de forma asíncrona con listeners encolados.
Con solo implementar la interfaz ShouldQueue, el listener se encolará automáticamente al despachar el evento.
<?php
namespace App\Listeners;
use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
// El worker de la cola lo ejecutará de forma asíncrona
}
}
Antes de usar listeners encolados necesitas configurar la cola y arrancar el worker.
Consulta la página de colas y jobs para más detalles.
Personalizar conexión, nombre de cola y retraso
Con atributos PHP puedes definir la conexión, la cola y el retraso.
<?php
namespace App\Listeners;
use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;
#[Connection('redis')]
#[Queue('emails')]
#[Delay(10)]
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
// ...
}
}
También puedes calcular los valores dinámicamente mediante métodos.
public function viaConnection(): string
{
return 'redis';
}
public function viaQueue(): string
{
return 'emails';
}
public function withDelay(UserRegistered $event): int
{
return 10;
}
Reintentos y timeout máximos
Con los atributos #[Tries] y #[Timeout] puedes controlar el comportamiento ante fallos.
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;
#[Tries(3)]
#[Timeout(30)]
class SendWelcomeEmail implements ShouldQueue
{
// ...
}
Gestión de fallos
Si defines el método failed, puedes ejecutar código adicional cuando el listener agote los reintentos.
use Throwable;
public function failed(UserRegistered $event, Throwable $exception): void
{
// Notificar al administrador, etc.
}
Suscriptores de eventos
Los suscriptores permiten agrupar varios handlers relacionados en una única clase.
Crear un suscriptor
En el método subscribe devuelves un array con la asignación entre eventos y handlers.
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
class UserActivitySubscriber
{
public function handleLogin(Login $event): void
{
// Lógica de login
}
public function handleLogout(Logout $event): void
{
// Lógica de logout
}
/**
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
Login::class => 'handleLogin',
Logout::class => 'handleLogout',
];
}
}
Registrar el suscriptor
Con el descubrimiento automático activado, los suscriptores cuyo subscribe devuelve un array se registran automáticamente.
Para registrarlos manualmente utiliza Event::subscribe en el método boot de AppServiceProvider.
use App\Listeners\UserActivitySubscriber;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::subscribe(UserActivitySubscriber::class);
}
Ejemplo práctico: enviar un correo de bienvenida al registrarse un usuario
Crear la clase de evento
php artisan make:event UserRegistered
Edita app/Events/UserRegistered.php para añadir una propiedad con el usuario registrado.<?php
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,
) {}
}
Crear la clase de listener
php artisan make:listener SendWelcomeEmail --event=UserRegistered
Para enviar el correo de forma asíncrona en la cola, implementa ShouldQueue.<?php
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->email)
->send(new WelcomeMail($event->user));
}
}
Despachar el evento desde el controlador
Llama a UserRegistered::dispatch() después de crear el usuario.<?php
namespace App\Http\Controllers\Auth;
use App\Events\UserRegistered;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class RegisterController extends Controller
{
public function store(Request $request): RedirectResponse
{
$user = User::create($request->validated());
UserRegistered::dispatch($user);
return redirect('/dashboard');
}
}
El RegisterController se limita a despachar el evento UserRegistered sin saber nada del envío del correo.
Si en el futuro quisiéramos «enviar además una notificación a Slack al registrarse», no habría que tocar el controlador. Arrancar el worker
Arranca el worker para procesar los listeners encolados.
Con el descubrimiento automático activado no hace falta registrar los listeners manualmente en AppServiceProvider.
Se detectan automáticamente todos los que estén en app/Listeners.
Con php artisan event:list puedes revisar los eventos y listeners registrados.
Revisa periódicamente que no haya listeners inesperados en la lista.