quitoque/queue-bundle
Quitoque QueueBundle is a Symfony bundle for queue-based background processing in PHP applications. It helps offload time-consuming tasks to workers, keeping request handling fast, with configurable queue and worker integration for asynchronous jobs.
This bundle is a Symfony background-job abstraction — not a Laravel package. If you're a Laravel developer, the mental model maps cleanly (dispatch() → queue → queue:work), but the wiring is Symfony DI + Doctrine, so the practical starting point is a decision:
composer require it. Read the transport classes under Queue/ / Transport/ as a reference for building Illuminate\Queue\Connectors\ConnectorInterface adapters. The strategic value is the swappable-transport pattern, not the code as a dependency.Read in this order before writing code: README.md → DependencyInjection/Configuration.php (the config tree enumerates every supported transport and its options) → the transport classes under Queue/ or Transport/. That directory is the real API surface.
Minimal setup (Symfony path):
Install
composer require quitoque/queue-bundle
Register the bundle
new Quitoque\QueueBundle\QuitoqueQueueBundle() in app/AppKernel.php.config/bundles.php. There is no Flex recipe for an unmaintained package — hand-write config/packages/quitoque_queue.yaml.Pick a transport and configure it
quitoque_queue:
transport: doctrine # or mongo | rabbitmq | beanstalkd | redis
# transport-specific connection options
Create the schema if you chose Doctrine ORM or Mongo ODM:
php bin/console doctrine:schema:update --force
# Mongo: doctrine:mongodb:schema:update
Confirm the job table/collection actually exists before dispatching anything.
First job + first worker. Inject the bundle's queue service and push a trivial message, then run the worker in a foreground terminal first:
php bin/console list | grep -i queue # confirm exact command name
php bin/console <queue-command> -vvv
First use case: a slow, non-interactive task (report generation, image resize, outbound email) dispatched from a controller — the Symfony analogue of dispatch(new SendWelcomeEmail($user)).
Storage-backed vs broker transports. With Doctrine/ORM or Mongo, the job is a row/document with a status lifecycle — you get free introspection (SELECT * FROM job WHERE status = 'pending') but polling and table growth are your problem. With RabbitMQ/Beanstalkd/Redis the job vanishes on ack — faster, no audit trail. Choose storage-backed when you need visibility/history; pick a broker for throughput.
Status lifecycle as your dashboard. On Doctrine transports, treat the job table as an operational dashboard during rollout. Log or expose counts of pending/running/failed per queue — this is your stand-in for Laravel Horizon, and it's the cheapest observability you'll get from an unmaintained package.
Per-job transport routing. The bundle's real selling point is pluggability. Define more than one transport (e.g. doctrine for slow report generation, rabbitmq for high-volume email) and route at the service/dispatch site rather than rewriting producers when requirements change. This is the "swap without touching producer code" lever — exercise it early so you validate the abstraction, not just one adapter.
Job handler shape. Keep each job a small, idempotent class: one public handle()/execute() method, serializable payload (IDs, primitives — never entities), no reference to services by object. Every handler should tolerate being run twice; at-least-once is the assumption for RabbitMQ and most brokers.
Worker supervision. Run workers under systemd/supervisord, one process per configured transport, and treat the worker command as the deploy boundary — restart on every deploy (long-lived workers hold stale code and broker sockets). Match --timeout / --tries to your handler latencies; the bundle's consumer command is the Symfony equivalent of php artisan queue:work.
Integration tip for Laravel-adjacent teams. The adapter method names you'll mirror when building Laravel connectors are the Laravel Queue contract: push, pushRaw, later, pop, size, clear. Reading the bundle's per-transport implementations as a checklist against those methods is the fastest way to port the abstraction without adopting the package.
Silent job loss on pop-without-ack. The classic failure for broker transports: a worker pops a message, dies mid-handle, and the message is gone. Audit the pop/delete sequence in whatever transport you use; prefer acknowledgements with a visibility timeout.
Duplicate execution. At-least-once delivery means retries produce double runs. Every handler must be idempotent — upserts, dedupe keys, or a "was this already processed" guard. This isn't a bundle bug; it's the semantics of the transports it wraps.
Poison messages with no retry ceiling. A message that always throws will block a worker indefinitely without a max-attempts cap. Confirm how the transport handles --tries / dead-letter routing before production; on Doctrine polling there may not be one, so implement a failed status transition yourself.
Blocked pop exceeding worker timeout. Blocking pop calls that outlive the worker's configured timeout get reaped mid-job, causing duplicate execution. Keep the blocking interval short and let the supervisor restart cleanly.
Connection exhaustion across deploys. Long-lived workers holding DB or broker sockets across a deploy leak connections. Restart workers on release; don't daisy-chain them.
Schema drift on Doctrine/Mongo. The bundle's Job entity/document changes across versions and may not match your ORM version's mapping conventions. Diff generated schema against the bundle's mapping before trusting schema:update --force — on legacy Symfony/PHP combinations, mapping metadata mismatches are common.
Config tree is the source of truth. DependencyInjection/Configuration.php tells you the exact keys; the README may be stale. When a transport silently ignores an option, check the tree — unrecognized keys under a bundle config are often silently dropped instead of erroring.
Extension point: the transport interface. The intended customization seam is your own transport driver ("… and write your own"). If you want to add a backend the bundle doesn't ship, implement the transport interface and register it as a tagged service — that's the cleanest path short of forking.
Debugging workflow. -vvv on the worker command, tail the worker log rather than the app log (worker processes write separately), and query the job table/broker depth directly when a job "disappears." Since there's no upstream triage, keep a local runbook covering: how to inspect depth, drain a queue, replay failed jobs, and rotate broker credentials.
Pin everything. PHP minor, doctrine/orm or doctrine/mongodb-odm major, Redis client (phpredis vs predis), RabbitMQ client (ext-amqp vs php-amqplib). These choices change both performance and whether the bundle's assumptions hold — and with zero dependents, you are the only maintenance layer.
How can I help you explore Laravel packages today?