dmstr/symfony-job-queue-bundle
Installation
composer require dmstr/symfony-job-queue-bundle
Register the bundle in config/bundles.php:
return [
// ...
Dmstr\JobQueueBundle\DmstrJobQueueBundle::class => ['all' => true],
];
Database Migration
Run migrations to create the job table:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
First Job Dispatch Define a job handler:
namespace App\MessageHandler;
use Dmstr\JobQueueBundle\Message\JobMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class ProcessUserDataHandler
{
public function __invoke(JobMessage $message): void
{
// Your business logic here
$message->setResult(['status' => 'processed']);
}
}
Dispatch a job via JobQueueService:
use Dmstr\JobQueueBundle\Service\JobQueueService;
$jobQueue = $this->container->get(JobQueueService::class);
$job = $jobQueue->dispatch(
'process_user_data',
['user_id' => 123],
ProcessUserDataHandler::class
);
Monitor Jobs
Access the API endpoint at /admin/jobs (if API Platform is configured).
Job Creation & Dispatch
Use JobQueueService::dispatch() to create a persisted job and enqueue it:
$job = $jobQueue->dispatch(
'job_name', // Unique identifier
['data' => 'payload'], // Serializable payload
ProcessJobHandler::class, // Handler class
['priority' => 5] // Optional options
);
JobMessage constraints.Handler Design
Extend JobMessage or use #[AsMessageHandler]:
#[AsMessageHandler]
class MyJobHandler
{
public function __invoke(JobMessage $message): void
{
try {
$result = $this->process($message->getData());
$message->setResult($result);
} catch (\Exception $e) {
$message->setError($e->getMessage());
}
}
private function process(array $data): array
{
// Business logic
}
}
Status Transitions
Jobs automatically transition through states:
pending → running (when dispatched) → done/failed (after handler execution).
Override transitions via JobMessage methods:
$message->setStatus(Job::STATUS_FAILED);
Retry Logic
Configure retries in config/packages/messenger.yaml:
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\JobMessage': async
failure_transport: failed
transports:
failed: 'doctrine://default?queue_name=failed'
retry_strategy:
max_retries: 3
delay: 1000
Bulk Operations
Use JobQueueService for batch dispatch:
$jobs = [];
foreach ($userIds as $id) {
$jobs[] = $jobQueue->dispatch('process_user', ['id' => $id], UserHandler::class);
}
doctrine, amqp, redis).$dispatcher->addListener(JobEvents::JOB_CREATED, function (JobEvent $event) {
// Log or notify on job creation
});
Job entity (configure in config/api_platform/resources.yaml):
resources:
Dmstr\JobQueueBundle\Entity\Job:
collectionOperations:
get:
method: 'GET'
path: '/admin/jobs'
normalization_context:
groups: ['job:read']
JobQueueService in tests:
$job = $this->createMock(Job::class);
$jobQueue = $this->createMock(JobQueueService::class);
$jobQueue->expects($this->once())
->method('dispatch')
->willReturn($job);
Payload Serialization
JobMessage::setData() or override JobMessage:
class CustomJobMessage extends JobMessage
{
public function __construct(array $data)
{
parent::__construct(json_encode($data)); // Custom serialization
}
}
Handler Execution Context
#[AsMessageHandler] with constructor injection for services:
#[AsMessageHandler]
class MyHandler
{
public function __construct(private MyService $service) {}
}
Database Locking
Job table locks. Optimize with:
status, created_at, updated_at.Status Polling
/admin/jobs for real-time updates. Use:
failure_transport for retries.API Platform CORS
/admin/jobs from a frontend, configure CORS in config/packages/nelmio_cors.yaml:
nelmio_cors:
paths:
'^/admin/jobs':
allow_origin: ['*']
allow_methods: ['GET']
allow_headers: ['Authorization']
Job Stuck in running?
php bin/console messenger:consume async -vv
$job = $entityManager->getRepository(Job::class)->find($id);
$job->setStatus(Job::STATUS_FAILED);
$entityManager->flush();
Handler Not Triggered?
config/packages/messenger.yaml:
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\JobMessage': async
#[AsMessageHandler].Payload Corruption
job table:
SELECT payload FROM job WHERE id = 123;
json_decode() to debug:
$payload = json_decode($job->getPayload(), true);
Custom Job Entity
Extend Job entity to add fields:
namespace App\Entity;
use Dmstr\JobQueueBundle\Entity\Job as BaseJob;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Job extends BaseJob
{
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $customField = null;
// Getters/setters
}
Custom Statuses
Add new status constants in Job:
final class Job extends BaseJob
{
public const STATUS_RETRYING = 'retrying';
// ...
}
Pre/Post Dispatch Hooks Subscribe to events:
use Dmstr\JobQueueBundle\Event\JobEvent;
use Dmstr\JobQueueBundle\Event\JobEvents;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(JobEvents::JOB_DISPATCHED)]
public function onJobDispatch(JobEvent $event): void
{
$job = $event->getJob();
// Add logic (e.g., send notification)
}
Custom Transport Integrate with non-Messenger transports (e.g., AWS
How can I help you explore Laravel packages today?