Vai al contenuto principale

Cosa sono le code

Nelle applicazioni web puoi avere operazioni che richiedono qualche secondo: invio email, ridimensionamento immagini, chiamate ad API esterne. Eseguirle sincronamente in una richiesta HTTP costringe l’utente ad aspettare. Con le code di Laravel esegui questi lavori in background in modo asincrono. La richiesta risponde subito e i worker si occupano del lavoro.
Le code supportano più backend: database, Redis, Amazon SQS, ecc. In sviluppo, con il driver sync i job vengono eseguiti immediatamente senza coda.

Configurazione delle code

config/queue.php

Impostazioni in config/queue.php. Cambia driver con QUEUE_CONNECTION.
'default' => env('QUEUE_CONNECTION', 'database'),

.env

QUEUE_CONNECTION=database

# Redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379

Preparazione del driver database

Serve una tabella jobs. In Laravel 11+ è già inclusa nella migration. Altrimenti:
php artisan make:queue-table
php artisan migrate

Preparazione del driver Redis

Aggiungi la connessione in config/database.php e installa il driver.
composer require predis/predis

SQS Overflow Storage

Amazon SQS ha un limite sulla dimensione dei payload. Per job con payload grandi puoi salvare l’eccesso in un cache store e inviare solo un puntatore.
'sqs' => [
    // ...
    'overflow' => [
        'enabled' => env('SQS_OVERFLOW_ENABLED', false),
        'store' => env('SQS_OVERFLOW_STORE'),
        'always' => false,
        'delete_after_processing' => true,
        'flush_on_clear' => env('SQS_OVERFLOW_FLUSH_ON_CLEAR', false),
    ],
],
  • enabled attivo: payload > 1MB salvati nel cache store.
  • always a true: salva sempre nel cache store.
  • delete_after_processing (default true): elimina i payload salvati al successo.
  • flush_on_clear a true: queue:clear esegue flush dello store di overflow. Usa uno store dedicato.

Creazione della classe job

make:job

php artisan make:job SendWelcomeEmail
Genera app/Jobs/SendWelcomeEmail.php.

Struttura del job

<?php

namespace App\Jobs;

use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public User $user,
    ) {}

    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
}
ShouldQueue indica che va elaborato via coda. Il trait Queueable fornisce i metodi necessari.
Passando un modello Eloquent al costruttore, Laravel serializza solo l’ID e ricarica i dati aggiornati in fase di esecuzione: payload più leggeri.

Dispatch del job

dispatch()

use App\Jobs\SendWelcomeEmail;

public function register(Request $request): RedirectResponse
{
    $user = User::create($request->validated());

    SendWelcomeEmail::dispatch($user);

    return redirect('/dashboard');
}

Ritardato

SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));

dispatchAfterResponse()

Esegui subito dopo la risposta HTTP. Funziona anche con sync.
SendWelcomeEmail::dispatchAfterResponse($user);

Coda specifica

SendWelcomeEmail::dispatch($user)->onQueue('emails');

Queue Routing

Instrada per default certi job a una connessione/coda tramite Queue::route() nel boot() di un ServiceProvider.
use App\Concerns\RequiresVideo;
use App\Jobs\ProcessPodcast;
use App\Jobs\ProcessVideo;
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
    Queue::route(RequiresVideo::class, queue: 'video');
}
Puoi indicare interfacce, trait o classi genitore. Più job insieme:
Queue::route([
    ProcessPodcast::class => ['podcasts', 'redis'],
    ProcessVideo::class => 'videos',
]);
Il Queue Routing è sovrascrivibile a livello di job con onQueue() / onConnection().

Esecuzione sincrona (test/sviluppo)

SendWelcomeEmail::dispatchSync($user);

Dispatch in bulk

Per molti job indipendenti senza tracciamento come nei batch:
use App\Jobs\ProcessUser;
use Illuminate\Support\Facades\Bus;

Bus::bulk(
    $users->map(fn ($user) => new ProcessUser($user))
);
Bus::bulk() invia in modo efficiente ma senza le funzionalità di batch (progresso, callback).

Elaborazione dei job

queue:work

php artisan queue:work
Con driver/coda specifici:
php artisan queue:work redis --queue=emails
php artisan queue:work database
queue:work resta in esecuzione. Riavvia con queue:restart dopo modifiche. In produzione usa Supervisor.

Opzioni del worker

php artisan queue:work --tries=3 --timeout=60 --sleep=3
OpzioneDescrizione
--tries=NTentativi max prima del fallimento
--timeout=NSecondi max per job
--sleep=NAttesa quando la coda è vuota (default 3)
--max-jobs=NTermina dopo N job
--max-time=NTermina dopo N secondi
--queue=A,BPriorità di code (A prima)

Impostazioni nella classe

use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(60)]
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;
}

Rilascio del job (middleware Release)

Per rimettere il job in coda al soddisfacimento di una condizione.
use Illuminate\Queue\Middleware\Release;

public function middleware(): array
{
    return [
        Release::when($this->order->isPending(), releaseAfter: 60),
    ];
}
Release::unless() rilascia quando la condizione è false.
return [
    Release::unless($this->order->isPaid(), releaseAfter: 60),
];
Con closure:
return [
    Release::when(function (): bool {
        return ! $this->order->isPaid();
    }, releaseAfter: 60),
];
Rilasciare incrementa il contatore dei tentativi. Configura correttamente #[Tries] o $tries.

Gestione dei job falliti

Tabella failed_jobs

php artisan make:queue-failed-table
php artisan migrate

Cleanup al fallimento

use Throwable;

public function failed(?Throwable $exception): void
{
    // Notifica ad admin, ecc.
}

Interrompere il retry per certe eccezioni

In withExceptions() di bootstrap/app.php:
use App\Exceptions\InvalidPodcastSourceException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetry([
        InvalidPodcastSourceException::class,
    ]);
})
Con closure:
use App\Exceptions\PodcastProcessingException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetryWhen(function (PodcastProcessingException $e) {
        return $e->reason() === 'Subscription expired';
    });
})
Per eccezioni deterministiche (validazione, sottoscrizione scaduta) è efficiente fallire subito.

Elenco job falliti

php artisan queue:failed

Retry

php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece
php artisan queue:retry all

Eliminazione

php artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece
php artisan queue:flush

Driver più usati

database

Semplice, senza middleware aggiuntivi. Salva su jobs e i worker fanno polling.
  • Pro: setup semplice, riusa RDBMS
  • Contro: carico DB, non adatto a grandi volumi
QUEUE_CONNECTION=database

redis

Il più usato in produzione. In memoria, alta throughput.
  • Pro: veloce, scalabile
  • Contro: richiede server Redis
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
In produzione considera Laravel Horizon per monitorare in tempo reale.

Esercizio in produzione con Supervisor

In produzione serve un supervisor che riavvii queue:work se cade.
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600
numprocs=2 per due worker in parallelo.
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

Esempio pratico: invio email in coda

1

Crea il job

php artisan make:job SendOrderConfirmation
2

Implementa

<?php

namespace App\Jobs;

use App\Models\Order;
use App\Mail\OrderConfirmed;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Support\Facades\Mail;

#[Tries(3)]
#[Timeout(30)]
class SendOrderConfirmation implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Order $order,
    ) {}

    public function handle(): void
    {
        Mail::to($this->order->user->email)
            ->send(new OrderConfirmed($this->order));
    }
}
3

Dispatch dal controller

use App\Jobs\SendOrderConfirmation;

public function store(Request $request): RedirectResponse
{
    $order = Order::create($request->validated());

    SendOrderConfirmation::dispatch($order);

    return redirect()->route('orders.show', $order)
        ->with('success', 'Ordine ricevuto.');
}
4

Avvia il worker

php artisan queue:work --tries=3 --timeout=30
  • Invio email/SMS
  • Elaborazione immagini/video
  • Chiamate ad API esterne
  • Generazione report o export CSV
  • Invio webhook
Con QUEUE_CONNECTION=sync i job vengono eseguiti subito senza worker.
QUEUE_CONNECTION=sync
php artisan queue:work
php artisan queue:restart
php artisan queue:failed
php artisan queue:retry all
php artisan queue:flush
Ultima modifica il 13 luglio 2026