Files
Laravel-Docker-Dev-Template/docs/queues.md
2026-03-06 08:57:05 +02:00

5.3 KiB

Queues & Background Jobs

This template includes Redis-powered queues for background processing.

Quick Start

# Start queue worker
make queue-start

# View queue logs
make queue-logs

# Stop queue worker
make queue-stop

# Restart after code changes
make queue-restart

Configuration

Queue is configured to use Redis in .env:

QUEUE_CONNECTION=redis
REDIS_HOST=redis
REDIS_PORT=6379

Creating Jobs

php artisan make:job ProcessOrder
<?php

namespace App\Jobs;

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

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

    public function __construct(
        public Order $order
    ) {}

    public function handle(): void
    {
        // Process the order
        $this->order->update(['status' => 'processing']);
        
        // Send notification, generate invoice, etc.
    }

    public function failed(\Throwable $exception): void
    {
        // Handle failure - log, notify admin, etc.
        logger()->error('Order processing failed', [
            'order_id' => $this->order->id,
            'error' => $exception->getMessage(),
        ]);
    }
}

Dispatching Jobs

// Dispatch immediately to queue
ProcessOrder::dispatch($order);

// Dispatch with delay
ProcessOrder::dispatch($order)->delay(now()->addMinutes(5));

// Dispatch to specific queue
ProcessOrder::dispatch($order)->onQueue('orders');

// Dispatch after response sent
ProcessOrder::dispatchAfterResponse($order);

// Chain jobs
Bus::chain([
    new ProcessOrder($order),
    new SendOrderConfirmation($order),
    new NotifyWarehouse($order),
])->dispatch();

Job Queues

Use different queues for different priorities:

// High priority
ProcessPayment::dispatch($payment)->onQueue('high');

// Default
SendEmail::dispatch($email)->onQueue('default');

// Low priority
GenerateReport::dispatch($report)->onQueue('low');

Run workers for specific queues:

# Process high priority first
php artisan queue:work --queue=high,default,low

Scheduled Jobs

Add to app/Console/Kernel.php or routes/console.php:

// routes/console.php (Laravel 11+)
use Illuminate\Support\Facades\Schedule;

Schedule::job(new CleanupOldRecords)->daily();
Schedule::job(new SendDailyReport)->dailyAt('08:00');
Schedule::job(new ProcessPendingOrders)->everyFiveMinutes();

// With queue
Schedule::job(new GenerateBackup)->daily()->onQueue('backups');

Start scheduler:

make scheduler-start

Module Jobs

When creating module jobs, place them in the module's directory:

app/Modules/Inventory/
├── Jobs/
│   ├── SyncStock.php
│   └── GenerateInventoryReport.php
<?php

namespace App\Modules\Inventory\Jobs;

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

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

    public $tries = 3;
    public $backoff = [60, 300, 900]; // Retry after 1min, 5min, 15min

    public function handle(): void
    {
        // Sync stock levels
    }
}

Job Middleware

Rate limit jobs:

use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Queue\Middleware\WithoutOverlapping;

class ProcessOrder implements ShouldQueue
{
    public function middleware(): array
    {
        return [
            new RateLimited('orders'),
            new WithoutOverlapping($this->order->id),
        ];
    }
}

Monitoring

View Failed Jobs

php artisan queue:failed

Retry Failed Jobs

# Retry specific job
php artisan queue:retry <job-id>

# Retry all failed jobs
php artisan queue:retry all

Clear Failed Jobs

php artisan queue:flush

Production Setup

For production, use Supervisor instead of Docker:

# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --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/html/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

Testing Jobs

use Illuminate\Support\Facades\Queue;

it('dispatches order processing job', function () {
    Queue::fake();
    
    $order = Order::factory()->create();
    
    // Trigger action that dispatches job
    $order->markAsPaid();
    
    Queue::assertPushed(ProcessOrder::class, function ($job) use ($order) {
        return $job->order->id === $order->id;
    });
});

it('processes order correctly', function () {
    $order = Order::factory()->create(['status' => 'pending']);
    
    // Run job synchronously
    (new ProcessOrder($order))->handle();
    
    expect($order->fresh()->status)->toBe('processing');
});