Appearance
Queue
Table of Contents
- Introduction
- Creating Tasks
- Dispatching Tasks
- Running Queue Workers
- Retries, Attempts and Timeouts
- Queue Drivers
- CLI Commands
- Configuration Reference
- Queue Facade Reference
- Testing Queue Tasks
Introduction
Phenix queues allow you to process work asynchronously using Tasks.
A queueable task is a class that extends Phenix\Tasks\QueuableTask (from src/Tasks), and is pushed/processed through the queue system in src/Queue.
Supported drivers:
paralleldatabaseredis
Creating Tasks
Generate a Queuable Task
Generate a queueable task:
sh
php phenix make:task SendWelcomeEmail --queueForce overwrite:
sh
php phenix make:task SendWelcomeEmail --queue --force
php phenix make:task SendWelcomeEmail --queue -fTask Class
PHP
<?php
declare(strict_types=1);
namespace App\Tasks;
use Amp\Cancellation;
use Amp\Sync\Channel;
use Phenix\Tasks\QueuableTask;
use Phenix\Tasks\Result;
class SendWelcomeEmail extends QueuableTask
{
protected ?int $maxTries = 5;
public function __construct()
{
$this->setTimeout(120);
}
protected function handle(Channel $channel, Cancellation $cancellation): Result
{
// ...
return Result::success(message: 'Welcome email sent');
}
}Dispatching Tasks
Basic Dispatch
PHP
use App\Tasks\SendWelcomeEmail;
SendWelcomeEmail::dispatch();Dispatch to a Specific Queue and Connection
Use enqueue() when you want to configure queue/connection before dispatching:
PHP
SendWelcomeEmail::enqueue()
->onQueue('emails')
->onConnection('default')
->dispatch();Conditional Dispatch
PHP
SendWelcomeEmail::dispatchIf(true);
$pending = SendWelcomeEmail::enqueueIf(fn (): bool => true);
$pending?->dispatch();Dispatch with Queue Facade
PHP
use App\Tasks\SendWelcomeEmail;
use Phenix\Facades\Queue;
Queue::push(new SendWelcomeEmail());
Queue::pushOn('emails', new SendWelcomeEmail());You can also consume manually:
PHP
$task = Queue::pop('emails');
$tasks = Queue::popChunk(10, 'emails');Running Queue Workers
Start the Worker
sh
php phenix queue:workYou can pass the connection as an argument:
sh
php phenix queue:work mysqlProcess a Single Iteration
sh
php phenix queue:work --once
php phenix queue:work -oProcess a Specific Queue
sh
php phenix queue:work --queue=emailsChunk Processing
Enable chunk mode:
sh
php phenix queue:work --chunksIn chunk mode, prefer one queue name per worker process.
Sleep Time
Seconds to sleep when no tasks are available:
sh
php phenix queue:work --sleep=1
php phenix queue:work -s 1Retries, Attempts and Timeouts
Execution flow:
- Worker executes
task->output(). - On success, the task is marked completed.
- On failure, the task is retried until max tries; then it is marked failed.
Max tries precedence:
QueuableTask::$maxTriesin your task class (if defined).- Otherwise worker option
maxTries(default:3).
Timeout is per task through setTimeout() / getTimeout() and enforced with TimeoutCancellation.
Queue Drivers
Parallel Driver
parallel uses in-memory queueing with async processing. I automatically starts processing after call push().
Database Driver
database stores tasks in a SQL table and reserves tasks atomically via transactions.
Generate the queue table migration:
sh
php phenix queue:table
php phenix queue:table --force
php phenix queue:table -fThen run:
sh
php phenix migrateGenerated migration filename:
database/migrations/20250101205638_create_tasks_table.php
Redis Driver
redis stores each payload under its task ID and uses queue-specific ready, reserved, and delayed structures. Lua scripts atomically perform push, pop/reserve, acknowledgement, retry and failure transitions.
If a worker dies after reservation, the task ID and payload remain available in Redis until the reservation can be recovered. Redis persistence and replication must be configured separately according to the application's durability needs. The next pop from that queue moves an expired task back to its original ready queue. Delivery is therefore at least once; task handlers must remain idempotent.
Configure the lease duration with REDIS_QUEUE_RESERVATION_TIMEOUT. It must be longer than the expected execution time of a task. A task still running after its lease expires may be reserved by another worker.
This key layout is incompatible with the previous Redis queue implementation. Drain old Redis queues before upgrading; existing queues:{name} payload lists are not migrated automatically.
Calling clear() removes pending and delayed tasks from the selected queue. It does not delete active reservations; those remain available for acknowledgement or recovery after their lease expires.
CLI Commands
sh
php phenix make:task SendWelcomeEmail --queue
php phenix queue:work
php phenix queue:work mysql --queue=default --sleep=3
php phenix queue:work --once
php phenix queue:work --chunks
php phenix queue:table
php phenix queue:table --force
php phenix queue:table -fConfiguration Reference
Queue configuration is defined in config/queue.php:
PHP
<?php
declare(strict_types=1);
return [
'default' => env('QUEUE_DRIVER', static fn (): string => 'database'),
'drivers' => [
'parallel' => [
'timeout' => env('PARALLEL_QUEUE_TIMEOUT', static fn (): int => 2),
'chunk_processing' => env('PARALLEL_QUEUE_CHUNK_PROCESSING', static fn (): bool => true),
'chunk_size' => env('PARALLEL_QUEUE_CHUNK_SIZE', static fn (): int => 10),
'max_retries' => env('PARALLEL_QUEUE_MAX_RETRIES', static fn (): int => 3),
'retry_delay' => env('PARALLEL_QUEUE_RETRY_DELAY', static fn (): int => 2),
'interval' => env('PARALLEL_QUEUE_INTERVAL', static fn (): float => 2.0),
],
'database' => [
'connection' => env('DB_QUEUE_CONNECTION', static fn (): string => 'mysql'),
'table' => env('DB_QUEUE_TABLE', static fn (): string => 'tasks'),
'queue' => env('DB_QUEUE', static fn (): string => 'default'),
],
'redis' => [
'connection' => env('REDIS_QUEUE_CONNECTION', static fn (): string => 'default'),
'queue' => env('REDIS_QUEUE', static fn (): string => 'default'),
'reservation_timeout' => env('REDIS_QUEUE_RESERVATION_TIMEOUT', static fn (): int => 60),
],
],
];Queue Facade Reference
Phenix\Facades\Queue methods:
- Queue operations:
push(QueuableTask $task)pushOn(string $queue, QueuableTask $task)pop(string|null $queueName = null)popChunk(int $limit, string|null $queueName = null)size()clear()getConnectionName()setConnectionName(string $name)driver(QueueDriver|null $driverName = null)
- Logging and fakes:
log()fake()fakeWhen(string $taskClass, Closure $callback)fakeTimes(string $taskClass, int $times)fakeOnce(string $taskClass)fakeOnly(string $taskClass)fakeExcept(string $taskClass)getQueueLog()resetQueueLog()resetFaking()
- Assertions helper:
expect(string $taskClass): Phenix\Testing\TestQueue
Testing Queue Tasks
Queue Logging
PHP
use App\Tasks\SendWelcomeEmail;
use Phenix\Facades\Queue;
Queue::log();
Queue::push(new SendWelcomeEmail());
$log = Queue::getQueueLog();
Queue::resetQueueLog();Faking Queue Pushes
PHP
use App\Tasks\SendWelcomeEmail;
use Phenix\Data\Collection;
use Phenix\Facades\Queue;
Queue::fake();
Queue::fakeOnly(SendWelcomeEmail::class);
Queue::fakeExcept(SendWelcomeEmail::class);
Queue::fakeOnce(SendWelcomeEmail::class);
Queue::fakeTimes(SendWelcomeEmail::class, 2);
Queue::fakeWhen(SendWelcomeEmail::class, function (Collection $log): bool {
return $log->count() < 3;
});
Queue::resetFaking();Queue Assertions
PHP
Queue::expect(SendWelcomeEmail::class)->toBePushed();
Queue::expect(SendWelcomeEmail::class)->toBePushedTimes(1);
Queue::expect(SendWelcomeEmail::class)->toNotBePushed();
Queue::expect(SendWelcomeEmail::class)->toPushNothing();Predicate assertion:
PHP
Queue::expect(SendWelcomeEmail::class)->toBePushed(function ($task): bool {
return $task !== null && $task->getQueueName() === 'emails';
});Production Behavior
Queue log/fake helpers are intentionally ignored in production environment.