Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Controller dispara Job::dispatch()"] --> B["Serialização do Job (SerializesModels)"]
    B --> C["Fila no Redis"]
    C --> D["Worker (queue:work) puxa o Job"]
    D --> E{"handle() lançou exceção?"}
    E -->|Não| F["Job concluído"]
    E -->|Sim, tries restantes| G["Reenfileira após backoff"]
    G --> D
    E -->|Sim, esgotou tries| H["failed_jobs + método failed()"]
    I["Laravel Horizon"] -.monitora.-> C
    I -.monitora.-> D

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style C fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style H fill:#ffebee,stroke:#c62828,stroke-width:2px
    style I fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px

🏛️ 1. Por que Enfileirar? Latência de Requisição vs. Trabalho Pesado

Qualquer tarefa que não precise de resposta imediata ao usuário (envio de e-mail, geração de PDF, chamada a uma API externa lenta, processamento de imagem) não deveria bloquear a thread da requisição HTTP. Um Job é uma classe PHP simples que implementa ShouldQueue; ao ser despachado, o Laravel serializa a instância (incluindo os Models Eloquent envolvidos, via trait SerializesModels, que grava apenas a chave primária e a recarrega do banco no worker) e publica esse payload na fila configurada:

<?php
namespace App\Jobs;

use App\Models\Pedido;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessarFaturaJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public Pedido $pedido) {}

    public function handle(): void
    {
        // Gera PDF, calcula impostos, chama gateway de pagamento, etc.
    }
}

O despacho é feito com ProcessarFaturaJob::dispatch($pedido). É possível atrasar a execução (->delay(now()->addMinutes(5))) ou direcionar para uma fila nomeada (->onQueue('faturas')), permitindo priorização.


⚠️ 2. Retries, Backoff e o Método failed() — o Gotcha Real de Produção

Um worker (php artisan queue:work) roda em processo de longa duração e não recarrega automaticamente o código PHP alterado — depois de um deploy, os workers precisam ser reiniciados (queue:restart) ou perderão a nova versão do código.

Falhas transitórias (timeout de rede, API externa fora do ar) não devem descartar o job imediatamente. As propriedades $tries e $backoff controlam quantas vezes e com qual intervalo o job será reprocessado:

<?php
public int $tries = 3;
public array $backoff = [10, 30, 60]; // 10s antes da 2ª tentativa, 30s antes da 3ª...

public function failed(\Throwable $exception): void
{
    \Log::error("Falha permanente ao processar fatura do pedido {$this->pedido->id}", [
        'erro' => $exception->getMessage(),
    ]);
    // Notificar equipe, gravar em failed_jobs (automático), etc.
}

Quando as tentativas se esgotam, o Laravel move o registro para a tabela failed_jobs e chama failed() — esse é o único lugar confiável para lógica de compensação (notificar o cliente, reverter estado), porque nesse ponto o job não será tentado novamente automaticamente. É possível reprocessar manualmente com php artisan queue:retry {id} ou --all.


📊 3. Laravel Horizon: Observabilidade sobre Filas Redis

Horizon substitui a operação “às cegas” de queue:work por um painel com throughput, taxa de falhas, tempo de execução por job e wait time por fila, além de gerenciar múltiplos processos de worker por meio de supervisors configurados em config/horizon.php:

<?php
// config/horizon.php
return [
    'defaults' => [
        'supervisor-1' => [
            'connection'   => 'redis',
            'queue'        => ['default', 'faturas'],
            'balance'      => 'auto',   // ajusta processos dinamicamente por carga
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
    ],
];

Horizon exige a conexão de fila configurada como redis (não funciona com o driver database ou sync) e roda como processo separado (php artisan horizon), tipicamente supervisionado por Supervisor ou systemd em produção.