laravel-appkit/lockable
Lockable adds record-level locking to Laravel Eloquent models, helping prevent concurrent edits and race conditions. Acquire, release, and auto-expire locks through a simple trait and fluent API, keeping your data safe in multi-user applications.
This is the initial v1.0.0 release, and the release notes are deliberately thin ("some very basic locking functionality"), so treat the source as the spec. Start in this order:
src/ — read the lock implementation itself; at v1.0.0 expect a thin wrapper over Laravel's cache lock primitives (Cache::lock()) or a database-backed lock. Confirm which.tests/ — the tests are the most reliable documentation of intended behavior at this maturity level.composer.json — note the supported Laravel/PHP versions and whether a specific cache driver is assumed.Minimal first use case: protect a single non-reentrant critical section — for example, a scheduled command that must not overlap, or a controller action that must not double-process a webhook. Acquire the lock, run the work inside a try, and release in finally. Verify behavior locally against your cache driver (Redis/Memcached/database) before trusting it in production, because "basic" locking packages often only support a subset of drivers.
Before adopting, pin the version and read the issue tracker — an initial release has no track record, and locking bugs manifest as silent double-execution rather than loud failures.
Guard a critical section with guaranteed release. Wrap acquisition and work so the lock is always freed, even on exceptions:
$lock = Locker::acquire('report:generate', ttl: 300);
if (! $lock) {
return; // someone else owns it
}
try {
$this->generateReport();
} finally {
$lock->release();
}
Block-with-timeout for contended work. If the operation should wait rather than skip, use a bounded wait so a stuck lock cannot hang a request or worker indefinitely. Prefer a short timeout plus a fallback (queue it, return 409, or log and skip).
Overlap protection in the scheduler. The most common day-to-day use is preventing overlapping scheduled tasks. Combine the package lock with withoutOverlapping() semantics, or use the lock alone if the package provides the finer control you need (per-tenant keys, custom TTL).
Per-resource lock keys. Namespace keys by the entity being protected (invoice:{$id}:reconcile) rather than using a single global key. This is where a lock wrapper earns its keep over ad-hoc Cache::lock() calls — but verify the package allows arbitrary string keys and doesn't impose a prefix you can't control.
Queued jobs and idempotency. For jobs that mutate shared state, acquire inside handle() and release in a finally. Combine with $tries/backoff so a lock miss is treated as a retry rather than a failure. Do not hold the lock across dispatch() of chained work — acquire in the child job instead.
Testing. Assert both the happy path and the contention path: acquire twice, assert the second returns falsy; assert release is called on exception. If the package exposes a fake or an in-memory driver, use it — otherwise point tests at the array cache driver and confirm the package respects it.
Integration tip: keep acquisition near the edge of the critical section (as late as possible) and release as early as possible. Holding a lock across HTTP calls to third parties is the fastest way to blow your TTL.
file and array drivers do not provide cross-process locking in the way Redis/Memcached/database do. A lock that works in a single-process test will not protect you across servers.delete-style release can free someone else's lock after a TTL rollover — check the implementation for this.KEYS, database lock table) to confirm keys are being written and deleted. Log acquire/release with the key and owner token; silent lock misses are the hardest failures to trace.^1.0 as ~1.0 for now. A package this young is likely to make breaking changes in minor releases while the API settles.How can I help you explore Laravel packages today?