zaber-dev/laravel-lock
Application-level distributed locking for Laravel with entity-scoped locks, automatic owner detection, and cleanup. Use cache or database storage, attach locks to Eloquent models, and protect routes with middleware to prevent race conditions.
Architecture fit
Cache::lock()) plus an optional database-backed store. It does not introduce a new distributed-systems runtime; it standardizes key derivation, ownership, lifecycle, and cleanup. That is a low-blast-radius shape for a Laravel monolith or a small set of Laravel services.Lock::for($action, $target)) maps well to the entity-scoped mutex pattern most Laravel apps hand-roll: billing_charge:{user_id}, webhook:{event_id}, payout:{account_id}. The HasLocks trait and polymorphic target resolution codify key naming that otherwise drifts across teams.LockAcquired, LockReleased, LockFailed, LockForceReleased) is the right hook point for observability and is the most valuable integration seam for a platform team.Integration feasibility
vendor:publish, one php artisan migrate if the database driver is used. Config is a single config/locks.php with a default driver and event toggle.Cache::lock() call sites can be left in place indefinitely alongside it.lock:action,ttl,driver) is a one-line route change and returns HTTP 429 automatically — attractive for webhook and payout endpoints, but it also silently imposes a per-request lock scope whose key derivation (user vs. IP vs. request hash) must be validated against real traffic before relying on it.Technical risk
block() helper does not mitigate this; it only guarantees release.forceRelease() is a loaded gun. It is operationally necessary for stuck locks but trivially misused as a "fix" for slow jobs, which converts a visible lock contention into silent double-writes.model:prune invocation. If that schedule is missed or misconfigured, the locks table grows unbounded and index contention degrades acquisition latency.Key questions
insertOrIgnore plus read-back), and how does it handle a lock row expiring between the read and the update?user_id, queue job ID, and request hash — and what happens in queue workers, Laravel Octane, and serialized closures where no request context exists?Stack fit
App\Support\Locking\LockManager) rather than used directly in domain code. Given adoption risk, the wrapper is the single most important integration decision.Migration path
Cache::lock() call site with Lock::for() behind the internal wrapper. Enable events and route them to existing logging/metrics. Compare acquisition and failure counts against the previous implementation.Compatibility
Cache::lock() usage on the same keys and prefixes — key prefix collisions between locks: and any hand-rolled convention will cause confusing cross-talk.locks table) and reversible.Sequencing
Maintenance
Lock::for() strings will proliferate and collide.Support
Cache::lock() or Redis SET NX + Lua scripts is a contained change of one or two classes.Scaling
Failure modes to monitor
ttl().LockFailed rates and alert on sustained contention for the same key.forceRelease() — ops unblocks a key that is actually mid-flight; manifests as duplicate side effects, not as an error.events.dispatch = true, every acquire/release becomes a listener invocation; high-frequency lock usage can generate meaningful event volume.Ramp-up
Cache::lock(), the README is example-driven, and the middleware is declarative. A mid-level Laravel developer is productive in under a day.forceRelease() outside a runbook.Overall recommendation
Approve for a bounded pilot on non-financial contention points with an internal wrapper and full event instrumentation. Do not adopt as the correctness mechanism for payment or ledger paths. Set an explicit review checkpoint — if upstream activity, dependents, or maintainer responsiveness do not improve, plan to absorb the code as an internal fork or revert to native Cache::lock() behind the same wrapper.
How can I help you explore Laravel packages today?