Jobs & Queue
Introduction
Modern applications often need to perform tasks that are too slow to handle within a typical HTTP request — sending emails, generating PDFs, processing images, or calling external APIs. Slenix's job queue system allows you to defer these time-consuming tasks to a background worker, keeping your API responses fast and your users happy.
Jobs are plain PHP classes that extend Slenix\Supports\Queue\Job. The queue driver is file-based by default, requiring zero additional dependencies — no Redis, no database tables, no extensions beyond what Slenix already uses.
Creating a Job
Use the Celestial CLI to generate a new job class:
php celestial make:job SendWelcomeEmailThis creates app/Jobs/SendWelcomeEmailJob.php:
<?php
declare(strict_types=1);
namespace App\Jobs;
use Slenix\Supports\Queue\Job;
class SendWelcomeEmailJob extends Job
{
public int $tries = 3;
public int $timeout = 60;
public function __construct(
// Inject dependencies via constructor
) {}
/**
* Execute the job.
*/
public function handle(): void
{
// Your job logic here
}
}All background logic goes inside handle(). Dependencies are injected through the constructor and serialised automatically with the job.
Writing a Job
A real-world job looks like this:
<?php
declare(strict_types=1);
namespace App\Jobs;
use Slenix\Supports\Queue\Job;
use App\Models\User;
class SendWelcomeEmailJob extends Job
{
/**
* Retry up to 3 times on failure.
*/
public int $tries = 3;
/**
* Abort if the job runs for more than 60 seconds.
*/
public int $timeout = 60;
/**
* Wait 10 seconds before retrying after a failure.
*/
public int $retryAfter = 10;
/**
* Push this job onto the 'emails' channel.
*/
public string $queue = 'emails';
public function __construct(private User $user) {}
public function handle(): void
{
(new Mail())->to($this->user->email)
->subject('Welcome to ' . env('APP_NAME'))
->message('<h1>Welcome, ' . $this->user->name . '!</h1>', true)
->send();
}
/**
* Called when all retry attempts are exhausted.
*/
public function failed(\Throwable $e): void
{
Log::error("Failed to send welcome email to {$this->user->email}: {$e->getMessage()}");
}
}Dispatching Jobs
After creating a job, dispatch it from anywhere in your application using the global dispatch() helper:
use App\Jobs\SendWelcomeEmailJob;
dispatch(new SendWelcomeEmailJob($user));The job is queued immediately and the current request continues without waiting for handle() to run.
Specifying a Queue Channel
// Use the job's own $queue property (default)
dispatch(new SendWelcomeEmailJob($user));
// Override the channel at dispatch time
dispatch(new SendWelcomeEmailJob($user), queue: 'emails');Delaying a Job
// Run 5 minutes from now
dispatch(new SendWelcomeEmailJob($user), delay: 300);
// Override both channel and delay
dispatch(new GenerateInvoiceJob($order), queue: 'pdfs', delay: 60);Using the Queue Facade Directly
use Slenix\Supports\Queue\Queue;
Queue::push(new SendWelcomeEmailJob($user));
Queue::push(new SendWelcomeEmailJob($user), queue: 'emails', delay: 30);Dispatching from a Controller
The most common pattern is dispatching jobs inside a controller after completing a database operation:
<?php
namespace App\Controllers;
use Slenix\Http\Request;
use Slenix\Http\Response;
use App\Models\User;
use App\Jobs\SendWelcomeEmailJob;
use App\Jobs\NotifyAdminJob;
use App\Jobs\GenerateAvatarJob;
class AuthController
{
public function register(Request $request, Response $response): void
{
$user = User::create([
'name' => $request->input('name'),
'email' => $request->input('email'),
'password' => hash_make($request->input('password')),
]);
// All three run in the background — response is instant
dispatch(new SendWelcomeEmailJob($user));
dispatch(new NotifyAdminJob($user));
dispatch(new GenerateAvatarJob($user), delay: 5);
$response->status(201)->json([
'success' => true,
'message' => 'Account created successfully.',
'data' => $user,
]);
}
}Running the Worker
Jobs are not processed automatically. You must start a worker process that continuously polls the queue and executes pending jobs.
Start the default worker:
php celestial queue:workStart a worker for a specific channel:
php celestial queue:work --queue=emailsPoll multiple channels in priority order:
php celestial queue:work --queue=emails,pdfs,defaultProcess one job and exit (useful for cron jobs):
php celestial queue:work --onceExit automatically when the queue is empty:
php celestial queue:work --stop-when-emptyControl the sleep interval between polls (default: 3 seconds):
php celestial queue:work --sleep=5Running Two Servers in Parallel
You typically run the HTTP server and the queue worker in separate terminals:
# Terminal 1 — HTTP server
php celestial serve
# Terminal 2 — Queue worker
php celestial queue:work --queue=emails,defaultInspecting Failed Jobs
When a job fails on all retry attempts it is moved to the failed channel. You can list all failed jobs with:
php celestial queue:failedClearing the Queue
Delete all pending jobs across all channels:
php celestial queue:clearDelete pending jobs from a specific channel:
php celestial queue:clear --queue=emailsQueue Storage
Jobs are stored as files in storage/queue/{channel}/. Each file is a JSON document containing the serialised job payload, attempt count, and availability timestamp.
storage/
└── queue/
├── default/
│ └── 1700000000_a1b2c3d4.job
├── emails/
│ └── 1700000060_e5f6g7h8.job
└── failed/
└── 1700000120_i9j0k1l2.jobYou can customise the storage path via the .env file:
QUEUE_DRIVER=file
QUEUE_PATH=storage/queueJob Properties Reference
| Property | Type | Default | Description |
|---|---|---|---|
$tries | int | 3 | Maximum number of attempts before marking as failed |
$timeout | int | 60 | Seconds before the job is force-stopped |
$retryAfter | int | 5 | Seconds to wait before retrying after a failure |
$queue | string | 'default' | Queue channel this job belongs to |
$delay | int | 0 | Seconds to delay execution after dispatch |
Worker Options Reference
| Option | Description |
|---|---|
--queue=name | Channel(s) to poll, comma-separated |
--sleep=N | Seconds to sleep when queue is empty (default: 3) |
--once | Process one job and exit |
--stop-when-empty | Exit when the queue is empty |
--max-jobs=N | Stop after processing N jobs |