Eventos e listeners no Laravel

1. Introdução aos Eventos e Listeners

Eventos e listeners no Laravel implementam o padrão Observer, permitindo que ações específicas disparem reações em cadeia sem acoplamento direto entre os componentes. Esse mecanismo é essencial para construir aplicações modulares, escaláveis e de fácil manutenção.

O fluxo básico funciona assim: um evento é disparado em algum ponto da aplicação (ex.: "pedido foi criado"), e todos os listeners registrados para aquele evento executam suas ações (ex.: enviar e-mail, atualizar estoque, registrar log). O Laravel gerencia todo o roteamento entre eventos e listeners.

2. Criando Eventos e Listeners

O Laravel oferece comandos Artisan para gerar as classes base:

// Gerar um evento
php artisan make:event OrderShipped

// Gerar um listener
php artisan make:listener SendShipmentNotification --event=OrderShipped

A estrutura de uma classe de evento é simples:

<?php

namespace App\Events;

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

class OrderShipped
{
    use Dispatchable, SerializesModels;

    public $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

Já o listener contém a lógica a ser executada:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Support\Facades\Log;

class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        $order = $event->order;

        Log::info("Notificação de envio para o pedido #{$order->id}");

        // Aqui você enviaria e-mail, SMS, notificação push, etc.
    }
}

O registro manual no EventServiceProvider conecta os dois:

protected $listen = [
    OrderShipped::class => [
        SendShipmentNotification::class,
        UpdateInventory::class,
    ],
];

3. Registro Automático e Descoberta de Eventos

Além do mapeamento manual, o Laravel 8+ oferece descoberta automática de eventos. Basta configurar no EventServiceProvider:

public function shouldDiscoverEvents(): bool
{
    return true;
}

O framework então escaneia seus listeners e eventos, associando-os automaticamente. Para otimizar performance em produção, use os comandos:

// Criar cache de eventos
php artisan event:cache

// Limpar cache de eventos
php artisan event:clear

Boas práticas: Use mapeamento manual em projetos complexos para ter controle explícito. A descoberta automática é ideal para prototipagem ou projetos menores.

4. Disparando Eventos

Existem duas formas principais de disparar eventos:

// Usando a facade
use Illuminate\Support\Facades\Event;
Event::dispatch(new OrderShipped($order));

// Usando o helper global
event(new OrderShipped($order));

O payload do evento é passado via construtor. Exemplo prático com dados do pedido:

class OrderShipped
{
    use Dispatchable;

    public function __construct(
        public Order $order,
        public string $trackingCode,
        public array $items
    ) {}
}

// Disparando
event(new OrderShipped($order, 'BR123456789', $order->items->toArray()));

O disparo pode ser síncrono (padrão) ou assíncrono usando filas.

5. Listeners com Fila (Queueable)

Para processamento assíncrono, implemente a interface ShouldQueue:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    public $connection = 'redis';
    public $queue = 'notifications';
    public $delay = 10; // segundos

    public function handle(OrderShipped $event): void
    {
        // Envio de e-mail pesado aqui
    }

    public function failed(OrderShipped $event, \Throwable $exception): void
    {
        // Registrar falha no envio
        Log::error("Falha ao enviar notificação: {$exception->getMessage()}");
    }

    public function retryUntil(): \DateTime
    {
        return now()->addMinutes(5);
    }
}

Isso permite que o envio de e-mails ou processamento de imagens não bloqueie a resposta HTTP.

6. Eventos Eloquent e Modelos

O Eloquent dispara eventos automaticamente durante operações no banco: created, updated, deleted, saved, restored, etc. Você pode mapeá-los no modelo:

<?php

namespace App\Models;

use App\Events\UserCreated;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $dispatchesEvents = [
        'created' => UserCreated::class,
    ];
}

Observers vs. Eventos: Observers agrupam vários listeners para um modelo em uma única classe. Use observers quando várias ações precisam reagir a diferentes eventos do mesmo modelo. Use eventos para ações transversais que afetam múltiplos modelos.

Exemplo de listener para log ao criar usuário:

class LogUserCreation
{
    public function handle(UserCreated $event): void
    {
        Log::channel('audit')->info("Usuário criado: {$event->user->email}", [
            'user_id' => $event->user->id,
            'timestamp' => now()
        ]);
    }
}

7. Listeners Condicionais e Eventos com Filtros

Listeners podem interromper a propagação de eventos para listeners subsequentes:

public function handle(OrderShipped $event): void
{
    if ($event->order->status !== 'paid') {
        return; // Não faz nada se não estiver pago
    }

    $event->stopPropagation(); // Impede que outros listeners executem
    $this->sendNotification($event->order);
}

Para testes, o Laravel fornece Event::fake():

public function test_order_shipped_triggers_notification()
{
    Event::fake();

    $order = Order::factory()->create();
    event(new OrderShipped($order));

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

8. Boas Práticas e Performance

  • Mantenha listeners pequenos — cada listener deve fazer apenas uma coisa (princípio da responsabilidade única)
  • Evite acoplamento — eventos não devem conhecer detalhes dos listeners, e vice-versa
  • Use cache de eventos em produçãophp artisan event:cache acelera o registro
  • Monitore filas — use Laravel Horizon ou ferramentas nativas para acompanhar listeners em fila
  • Priorize listeners críticos — use filas diferentes para notificações, logs e processamento pesado

Exemplo de listener otimizado:

class UpdateInventory implements ShouldQueue
{
    public $queue = 'inventory';

    public function handle(OrderShipped $event): void
    {
        foreach ($event->order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }
}

Eventos e listeners no Laravel oferecem uma arquitetura elegante para desacoplar lógicas de negócio. Quando bem implementados, tornam o código mais testável, modular e preparado para escalar.

Referências