m6web/daemon-bundle
Symfony bundle for turning console commands into long-running daemons. Extend the provided daemon command, implement your loop, and get signal handling, graceful shutdown, and simple process lifecycle management without boilerplate. Ideal for workers, queue consumers, and background jobs.
Architecture fit — unchanged at the layer, marginally better at the seam. m6web/daemon-bundle (now BedrockStreaming/DaemonBundle) remains a Symfony bundle: Bundle subclass, registerBundles(), container extension, config tree. None of that exists in Laravel, so DaemonBundle::boot() stays inert and the transferable surface remains the abstract daemonized-command base class on Symfony\Component\Console\Command\Command. What v9.0.0 changes is the composition of that surface: signal handling has moved out of a method override and into a ConsoleCommandListener registered against the Symfony Console event dispatcher. That is a meaningful shift in our favour — a listener is a portable concept that Laravel's Artisan can dispatch, whereas an overridden handleSignal() was reachable only by inheritance. Re-estimate transferable value at roughly 30–40%, up from 20–30%, still on the fork-and-adapt path.
Integration feasibility — medium-low, now with a narrower fork. Path (a) (vendor the base class into app/Console/Daemons/) and path (b) (internal package with ServiceProvider + Artisan::starting()) both stand, but the listener refactor moves roughly the same code out of the class and into a registration hook. For us, that is a net simplification: our internal abstraction can compose a listener via Artisan::starting() rather than requiring every daemon to extend a bespoke base class. Both paths remain multi-sprint for a senior engineer because the loop, drain semantics, and container lifecycle are still ours to own — the release did not add any Laravel-facing integration.
Technical risk.
handleSignal is removed from the bundle in favour of ConsoleCommandListener. Any fork or vendored copy that overrode handleSignal breaks on upgrade. Critically, Symfony 7.3 introduces its own handleSignal on the command layer, so a stale vendored override can now produce a signature-compatibility fatal, not just a silent behaviour change. Our spike branch must be audited for this before any Symfony bump.composer.json constraints must be read, not assumed. A package requiring ^7.3 will not install on a Laravel 11 app locked to Symfony 7.0–7.2.pcntl and ext-posix remain hard requirements. Still unavailable on Windows, often absent from minimal php:cli images. Verify across the fleet before design work.DB::reconnect(), scoped bindings never resetting, unbounded memory from models and query logs, and no built-in --max-time/--memory guardrails analogous to queue:work.queue:work, Horizon, schedule:run under cron, Octane for HTTP. The risk of introducing a second, non-idiomatic concurrency model is not reduced by this release.Key questions for the team.
symfony/console / symfony/http-kernel constraints, and does that intersect our composer.lock on Laravel 11 and 12?ConsoleCommandListener register against the Symfony Console event dispatcher only, or is there bundle-side glue that would need re-implementation for an Artisan::starting() hook?pcntl/posix enabled, and is Windows or ephemeral-dyno hosting in scope?SIGTERM, or exit mid-iteration?Cache::lock, Redis, or nothing?Stack fit. Laravel and this package still intersect only at symfony/console. v9.0.0 makes that intersection slightly richer — console event listeners are a supported extension point — but the kernel, container, DI extension, and config tree remain foreign. Treat the package as a reference implementation, now with a clearer architectural cue: model our internal AbstractDaemonCommand around a listener-driven signal trap rather than a method override, since that is both upstream's direction and the pattern that survives Symfony minor bumps.
Migration path. Sequenced:
composer.json against our app locks. If the Symfony floor exceeds our pin, the evaluation ends here.supervisord; measure RSS over 24h; confirm SIGTERM drain and that no handleSignal override collides with Symfony 7.3.--max-iterations, --memory-limit, DB::reconnect(), per-work-item Cache::lock(), structured logging, liveness heartbeat, and explicit listener-registration idempotency.app/Console/Daemons (or a private composer package) with a ServiceProvider, an Artisan::starting() hook, and a publishable supervisor unit template.Compatibility. PHP 8.1+, Unix only, CLI SAPI only. Must not run under FPM/CGI. Symfony 7.3 is now the reference target for signal handling — verify our Laravel versions can meet it, and note that Laravel Octane is a different process model and must not be conflated with this.
Sequencing recommendation. Still: do not composer require this into a long-lived application. Fork-and-adapt remains lower-risk and lower-TCO, and v9.0.0 slightly improves the payoff of that fork because the listener is easier to re-host than an inherited method. But the release also proves the fork must be maintained against a moving Symfony target — budget for that explicitly.
Maintenance. Signal on the project is mixed but net-neutral-to-slightly-positive: v9.0.0 is a real compatibility fix landed promptly against a Symfony 7.3 conflict, and it came from a contributor outside the original vendor (@pforesi), which is a modest improvement on bus factor. It is still one PR against a repo with ~59 stars and no dependents, and the release is a major version bump — upstream is willing to break its API to track Symfony, which is correct engineering but means our fork inherits a moving target. Keep a named owner and a quarterly review; add a specific trigger to that review: any Symfony Console release that touches signal handling.
Support. Unchanged. No Laravel-community channel, no Laravel-oriented docs, no Stack Overflow corpus. Debugging falls back to Symfony bundle source plus, now, Symfony Console's own signal-handling internals. Internal runbook must be extended with one new item: how to confirm the console listener is registered and firing (a startup log line at registration, plus a heartbeat).
Scaling. Unchanged. One process per daemon; no distribution or leader election. Horizontal scale means N supervisors with Cache::lock()/Redis coordination and idempotent handlers. Memory growth remains the primary ceiling — plan supervisord with stopwaitsecs, autorestart, and a max-memory restart trigger.
Failure modes to instrument for.
SIGKILLed on deploy, or a handler that fires twice. Mitigation: assert single registration at boot and log it.SIGTERM-first restart policy with drain timeout.SIGKILL: mitigation: idempotent work units and transactional writes.DB::reconnect() at loop top with backoff.Ramp-up. Non-trivial and essentially unchanged. Engineers need Unix signals, event-loop semantics, long-lived PHP memory behaviour, and the Laravel container's lifecycle across iterations. v9.0.0 adds a small amount of required literacy — Symfony Console's event dispatcher and where Laravel's Artisan exposes it — but does not move the estimate: one to two sprints for a senior engineer to reach production confidence on the first command, plus a sprint to make it a repeatable pattern.
TPM recommendation. Low strategic value in its current form. v9.0.0 is a credible, timely compatibility fix that modestly improves the case for a fork and does nothing for the case for a dependency. Approve a one-week spike only if the workload genuinely does not fit queue:work, Horizon, or schedule:run — and gate that spike on question 2 (Symfony constraint intersection) being answered first, because a version conflict there closes the investigation for free. Expected outcome remains a small internal abstraction inspired by this package.
How can I help you explore Laravel packages today?