ffreitas-br/command-lock-bundle
A Symfony bundle for locking console commands so scheduled jobs and long-running tasks don’t run concurrently. Prevent duplicate executions and race conditions by applying locks to individual commands with minimal configuration.
Architecture fit — poor, effectively nil for Laravel as-published. This is a Symfony bundle (*Bundle naming, DI extension, Resources/config service wiring), not a Laravel package. Laravel has no bundle registration concept — it expects a ServiceProvider, optional Facade, and publishable config. The only shared substrate is that Laravel's Artisan console is built on symfony/console, so the idea (a lock guard around command execution) is portable, but the artifact is not. Any adoption requires a port, not an install.
Integration feasibility — low without a rewrite. The bundle targets the Symfony 2.2–2.5 era (last release 2014-06-02). At that time symfony/lock did not exist (it shipped in 2.6/3.0), so the lock primitive is almost certainly a custom flock-on-a-file or a cache-backed flag. Two consequences:
Cache::lock() or the scheduler's withoutOverlapping().Technical risk — high and asymmetric. Risks concentrate on the runtime, not the concept:
phpstan/rector) before anything else.Symfony\Component\Console\Command\Command and EventDispatcher signatures changed materially (e.g., Command::execute return type, configure() conventions). A shim layer is unlikely to be cheaper than a rewrite.Key questions for the maintainer/vendor (or for the spike):
flock, mkdir atomicity, Memcached add, Redis SETNX?SIGKILL leave the command permanently blocked?finally/shutdown handler, or only on graceful exit?Stack fit — the requirement is valid; this package is the wrong vehicle. Concurrent-invocation suppression is a real operational need for scheduled jobs, webhook-triggered Artisan commands, and queue workers. Laravel has shipped first-class answers to this since 5.x/6.x, which means the buy-vs-build calculus points at the framework, not at a 2014 Symfony bundle.
Recommended path (in order of preference):
Native Laravel primitives — no dependency.
$schedule->command('foo')->withoutOverlapping(10) for per-task mutual exclusion.Cache::lock("cmd:foo", 300)->get() with ->block(0) for fail-fast behavior; wrap in try/finally with $lock->release().symfony/lock standalone — if you need richer semantics. Current 6.x/7.x releases are actively maintained, support Redis/Postgres/Doctrine/flock stores, and offer LockFactory::createLock() with configurable TTL and auto-release. Installable via Composer into Laravel with no bundle plumbing. This is the closest legitimate successor to what this package attempted in 2014.
Fork-and-port — only if 1 and 2 demonstrably fail. Extract the lock algorithm, reimplement as a Laravel package with a ServiceProvider, a config/command-lock.php, and a Command decorator or middleware. Budget 3–5 engineer-days plus test coverage; assume the original is a specification, not a codebase.
Install as-is — not recommended under any scenario. Blocked by bundle/container mismatch and PHP version risk.
Migration path and sequencing:
flock semantics suffice.Cache::lock() on the production cache store. Include a forced-failure test (kill -9 mid-execution) to confirm TTL releases the lock.withoutOverlapping() to the scheduler; add lock guards to any command invoked by HTTP or queue. Instrument with log lines on lock-acquired/lock-skipped.Compatibility notes: withoutOverlapping() requires the cache store to be shared across all hosts running the scheduler — verify you are not on the array or file driver for the scheduler mutex if you run more than one scheduler node. Cache::lock() requires Laravel 6+ and a store implementing LockProvider (Redis, Memcached, DynamoDB, database).
Maintenance — the package itself is a liability, not an asset. One release, June 2014, single author, no dependents. There is no realistic path to upstream fixes, and no security disclosure process. Even in a fork scenario you inherit the maintenance burden entirely. By contrast, symfony/lock and Laravel's cache locks receive ongoing security and PHP-version support.
Support — zero. No documentation beyond the README, no test suite evidence, no issue triage. Any incident investigation lands on your team. For a component that gates whether production jobs run at all, this is an unacceptable support posture.
Scaling — the likely weak point. File-based locking (the most probable 2014 implementation) degrades in three ways that matter at scale:
flock is host-local. Two app servers both acquire the lock and both run the job. This silently defeats the entire purpose in any horizontally scaled deployment.flock over NFS/EFS is historically unreliable and filesystem-dependent.A Redis-backed lock (Cache::lock) resolves all three and is the reason to prefer the native route.
Failure modes to design against:
| Failure | Consequence | Mitigation |
|---|---|---|
Process killed mid-run (no finally) |
Lock held until TTL or forever | Mandatory TTL; verify store expiry |
| TTL shorter than job runtime | Second instance starts while first still running | Size TTL to p99 runtime × 2; log overruns |
| Lock store unavailable (Redis down) | Either all jobs blocked or all run | Decide fail-open vs fail-closed per command and document it |
| Clock skew across hosts | TTL expiry miscalculated | Use store-side TTL (SETEX), not app-side timestamps |
| Bundle not registered / silently no-op | False confidence, duplicate execution | Integration test asserting the second invocation is actually skipped |
Ramp-up — low for the recommended path, moderate for a fork. Any Laravel engineer can adopt withoutOverlapping()/Cache::lock() in under a day; the concepts are documented and idiomatic. A fork-and-port requires someone comfortable reading Symfony 2.x container XML/YAML and reconstructing intent from undocumented 2014 code — call it 1–2 days of archaeology before any code is written, with meaningful risk of misreading the original's guarantees.
Bottom line for the roadmap: the capability is worth prioritising — unsuppressed concurrent command execution is a classic source of duplicated charges, double-sent emails, and corrupted batch state. But ffreitas-br/command-lock-bundle should be logged as a rejected candidate: wrong framework, abandoned, unverifiable correctness. Deliver the same outcome via Laravel's native locking, with symfony/lock as the escape hatch if distributed or cross-language requirements emerge.
How can I help you explore Laravel packages today?