serendipity_hq/bundle-commands-queues
Serendipity HQ Bundle Commands Queues provides console commands for managing queue workflows in Symfony/PHP applications, helping you run workers, inspect queue activity, and integrate queue operations into command-line processes.
First, the honest caveat: this is a Symfony bundle (SerendipityHQ\Bundle\CommandsQueuesBundle), not a Laravel package. There is no service provider, no config/queues.php, and no php artisan integration out of the box. You cannot composer require it into a Laravel app and expect it to work. Treat it as (a) a tool for Symfony services in a mixed stack, or (b) a reference implementation for the "persisted command queue" pattern if you're building the equivalent on top of Laravel's queue system.
If you are in a Symfony app:
composer require serendipity_hq/bundle-commands-queuesconfig/bundles.php.DependencyInjection/Configuration.php for the config tree (table/entity overrides, worker tuning) before writing any glue code.Where to look first, in this order:
Command/ directory — there will be a console command that acts as the consumer/worker. That command's options are your real API surface.Entity/ directory — this tells you exactly what state is persisted (status transitions, retry counters, timestamps).*Queue* manager/service) — this is what you inject into your own services.First use case: take one slow, fire-and-forget console command you already have (report generation, cache warm, third-party sync) and enqueue it from a controller instead of running it inline. Then run the consumer command in a terminal and watch a row move through its statuses. That single loop teaches you 80% of the bundle.
Enqueue-don't-execute. The core win is turning $command->run() into "persist intent, return immediately." Inject the queue service into your controller/service, add the command with its arguments, return a response. The HTTP request no longer pays the cost.
Worker as a supervised daemon. The consumer command is meant to run as a long-lived process, not be invoked per-request. Under systemd/Supervisor, with --env=prod, restart-on-failure enabled. In a Laravel context this is the same shape as queue:work vs. dispatch() — the mental model transfers cleanly.
Queue-per-concern. Use separate named queues for priority tiers (critical / default / bulk) rather than one giant queue, and run one worker pool per tier. This prevents a 10-minute report from starving a 2-second notification.
Enqueue from domain events, not controllers. A listener on OrderPlaced that enqueues the fulfillment command keeps your HTTP layer thin and makes the enqueue point testable in isolation. This is the pattern that scales; controller-side enqueueing rots fast.
Prefer the package for command-level work, Laravel's queue for job-level work. If your stack is genuinely mixed, let the Symfony side own its console commands and let Laravel own its jobs. Bridging the two (Laravel dispatches a job whose only purpose is to hit a Symfony consumer) adds a failure domain for little gain.
Laravel translation, if you're porting the idea: map queued command → ShouldQueue job, consumer → queue:work, retry metadata → tries/backoff, queue names → Laravel's onQueue(). The bundle's persisted-status model is essentially what the jobs and failed_jobs tables already give you.
Doctrine's EntityManager in a long-running worker is the #1 footgun. The EM accumulates managed entities until memory exhausts, and stale entities mask state changes. If you touch Doctrine inside the worker loop, clear the EM periodically and re-fetch. This is the direct analogue of Laravel's --memory/queue:restart discipline.
Enqueue after commit, not inside the transaction. If you enqueue inside a DB transaction that later rolls back, the worker may pick up a command referencing rows that never existed. TransactionWatcher-style post-commit dispatch exists for a reason.
Serialization constraints on arguments. Command arguments must round-trip through whatever storage the bundle uses. Pass scalars and IDs, not entities or closures. Resolve the object inside the command from its ID.
Exit codes are the contract. A console command returning non-zero should be what drives retry/failure status. If your command swallows exceptions and returns 0, the queue will mark a broken job as succeeded and you'll debug it for a week.
Restart workers on deploy. Long-running processes hold old code in memory. Every deploy needs a worker recycle; bake it into your release script.
Watch for lock contention with multiple workers. If the bundle's claim mechanism isn't SELECT ... FOR UPDATE SKIP LOCKED-style, two workers can grab the same row. Verify this in the source before scaling past one consumer — and if it's not there, don't scale past one.
Set an explicit timeout per command. A hung command occupies a worker slot forever. Whatever the bundle exposes for max runtime, wire it to your slowest acceptable job, not to "infinite."
Monitor the failed pile, not just the queue depth. Queue depth tells you about load; failed rows tell you about correctness. Alert on growth of the failed/abandoned set.
10 stars, 0 dependents means you own the debugging. Read Entity/ and the consumer command end-to-end before production. There is no Stack Overflow corpus for this package — the source is the documentation, and the bus factor is effectively one maintainer.
Extension point to remember: because entities are Doctrine-mapped and configurable, you can often attach your own metadata (tenant id, correlation id) via a joined entity or a subclass rather than patching the bundle. Prefer that to forking.
How can I help you explore Laravel packages today?