0

Creating and dispatching jobs — make:job, dispatch(), dispatchAfterResponse()

Intermediate5 min read·lv-19-002

Concept

Job configuration controls retry behavior, timeout, and queue selection. These properties are set on the job class itself or overridden at dispatch time.

$tries: Maximum number of attempts including the first. public int $tries = 3 → if the job throws 3 times, it's marked as failed.

$maxExceptions: Alternative to $tries — limits the number of exceptions before failing, even if retries remain.

$timeout: Maximum seconds a job can run. If exceeded, the worker is killed and the job is released back to the queue. Requires pcntl extension. Default: 60 seconds.

$backoff: Seconds to wait before retrying after a failure. Can be an array for exponential backoff: public array $backoff = [1, 5, 10] → wait 1s, then 5s, then 10s.

$retryUntil(): Method instead of $tries — retry until a specific time. return now()->addHours(2) — keep retrying for 2 hours.

$failOnTimeout: If true, timeout counts as a failure toward $tries. Default: false.

Releasing back to queue: $this->release(int $seconds = 0) — manually put the job back in the queue with optional delay. The attempt count is incremented. Use when you know you should retry later (e.g., external API rate limit response).

$this->fail($exception): Mark job as failed immediately without retrying.

failed(Throwable $exception): Hook called when all retries are exhausted. Use for cleanup, notifications.

Code Example

php
<?php
namespace App\Jobs;

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

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

    // Retry configuration
    public int    $tries   = 5;          // up to 5 attempts
    public int    $timeout = 30;         // 30 seconds max
    public bool   $failOnTimeout = true; // timeout counts as failure

    // Exponential backoff between retries
    public function backoff(): array
    {
        return [1, 5, 30, 60, 120]; // 1s, 5s, 30s, 1m, 2m between retries
    }

    public function __construct(private readonly int $orderId) {}

    public function handle(): void
    {
        $order = \App\Models\Order::findOrFail($this->orderId);

        try {
            $result = app(\App\Services\PaymentGateway::class)->charge($order);
        } catch (\App\Exceptions\RateLimitedException $e) {
            // API rate-limited — release back to queue for 60 seconds
            $this->release(60);
            return;
        } catch (\App\Exceptions\CardDeclinedException $e) {
            // Card declined — no point retrying
            $this->fail($e);
            return;
        }

        $order->update(['status' => 'paid', 'payment_id' => $result->id]);
    }

    // Called after all retries are exhausted
    public function failed(\Throwable $exception): void
    {
        $order = \App\Models\Order::find($this->orderId);
        $order?->update(['status' => 'payment_failed']);
        \Illuminate\Support\Facades\Mail::to($order?->customer)
            ->send(new \App\Mail\PaymentFailedNotification($order));
    }
}

// retryUntil instead of $tries
class SyncCrmJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function retryUntil(): \Illuminate\Support\Carbon
    {
        return now()->addHours(6); // keep retrying for 6 hours
    }

    public function handle(): void
    {
        // Try to sync with CRM — retry until 6h window expires
    }
}