generated from theradcoza/Laravel-Docker-Dev-Template
276 lines
5.3 KiB
Markdown
276 lines
5.3 KiB
Markdown
# Queues & Background Jobs
|
|
|
|
This template includes Redis-powered queues for background processing.
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```env
|
|
QUEUE_CONNECTION=redis
|
|
REDIS_HOST=redis
|
|
REDIS_PORT=6379
|
|
```
|
|
|
|
## Creating Jobs
|
|
|
|
```bash
|
|
php artisan make:job ProcessOrder
|
|
```
|
|
|
|
```php
|
|
<?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
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```bash
|
|
# Process high priority first
|
|
php artisan queue:work --queue=high,default,low
|
|
```
|
|
|
|
## Scheduled Jobs
|
|
|
|
Add to `app/Console/Kernel.php` or `routes/console.php`:
|
|
|
|
```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:
|
|
|
|
```bash
|
|
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
|
|
<?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:
|
|
|
|
```php
|
|
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
|
|
|
|
```bash
|
|
php artisan queue:failed
|
|
```
|
|
|
|
### Retry Failed Jobs
|
|
|
|
```bash
|
|
# Retry specific job
|
|
php artisan queue:retry <job-id>
|
|
|
|
# Retry all failed jobs
|
|
php artisan queue:retry all
|
|
```
|
|
|
|
### Clear Failed Jobs
|
|
|
|
```bash
|
|
php artisan queue:flush
|
|
```
|
|
|
|
## Production Setup
|
|
|
|
For production, use Supervisor instead of Docker:
|
|
|
|
```ini
|
|
# /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
|
|
```
|
|
|
|
```bash
|
|
sudo supervisorctl reread
|
|
sudo supervisorctl update
|
|
sudo supervisorctl start laravel-worker:*
|
|
```
|
|
|
|
## Testing Jobs
|
|
|
|
```php
|
|
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');
|
|
});
|
|
```
|