sanchescom/laravel-cache-memory
composer require sanchescom/laravel-cache-memory and confirm the required extensions are present (php -m | grep -E 'shmop|sysvsem|pcntl') — the driver relies on SysV shared memory plus a semaphore, and pcntl only matters for the package's own forked tests.config/cache.php using the driver key documented in the package README, then point CACHE_STORE at it for the environments where it makes sense (typically local, CI, and single-host production).php artisan memory:stats. It prints the segment key, usable capacity, bytes used/free, item count, and expired count — read under the lock and without purging, so it is safe to run against a live segment while you size the store.Cache::add(). Cache::add('job:'.$id, true, 300) now returns false for exactly one loser per key across processes, which is the cheapest distributed dedupe/mutex primitive you get from this driver.Single-flight / idempotency across queue workers. The atomic add() is the workhorse. Wrap work that must run once per key:
if (! Cache::add("import:{$fileId}", 1, 900)) {
return; // another worker already owns this
}
Because add() runs inside the store's semaphore, this is correct on this driver without a get() + put() race — you can use it for webhook dedupe, cron overlap guards, and one-time notification dispatch.
Cache::lock() for read-modify-write. Use it anywhere you compose multiple cache operations, and for anything not guaranteed atomic (counters, allow/deny logic, sequence allocation):
Cache::lock('counter:orders', 10)->block(5, function () {
$value = (int) Cache::get('orders', 0) + 1;
Cache::put('orders', $value, 3600);
});
Or the explicit form when you need the critical section to span a queue dispatch:
$lock = Cache::lock('report:build', 120);
if (! $lock->get()) { return; }
try {
// ...
} finally {
$lock->release();
}
Acquisition goes through add() internally, so seconds = 0 behaves correctly (unlike the framework's generic CacheLock), and release() is owner-checked in a single critical section — an expired lock re-acquired by another process will not be deleted by the stale holder. block(), restoreLock(), and forceRelease() behave as on other drivers.
Namespace your lock keys. Lock names share the key space with cache keys, as with every cache-backed lock. Adopt a convention up front (lock:, data:, dedupe:) so flush() and stats stay legible and you never collide a lock with a data key.
Existing Cache::lock() call sites need no change. MemoryStore implements LockProvider, so anything already calling Cache::lock() — including framework features and third-party packages that type-hint the lock provider — now works against this driver. That is the main reason to upgrade: it removes the previous "works with Redis, silently degrades here" divergence in your test suite.
Operational integration. Use memory:stats in health checks and deploy smoke tests. Pair it with an alert on bytes free trending toward zero, and scrape/inspect it however you already handle single-host state. If you need metrics in Prometheus or your APM, call the store's stats() from a small scheduled command rather than shelling out to Artisan.
E_USER_WARNING and discards everything in the segment — including held locks. If you mask warnings (custom error handler, @, log-level filters), you only have add()'s new false return to tell you the write was lost. Always check the return value of add(); never assume a lock acquired if it returned false.refresh() is not supported. Do not write code paths that extend a lock TTL; choose a TTL longer than the worst-case critical section, or release and re-acquire. restoreLock() still works for the queue-job lock pattern.flush(), requestDeletion(), and the overflow purge all drop held locks alongside the data. Never flush from a deploy hook or a maintenance command while workers are running.memory:stats when debugging "impossible" cross-app cache hits.forceRelease() clobbers ownership. It bypasses the owner check by design. Reserve it for tests and operator tooling, never for routine cleanup.add() code you depend on.Repository::add() with a TTL is now atomic across processes, with unchanged return semantics — except the overflow case now returns false instead of appearing to succeed. If you had worked around the old non-atomicity with your own lock around add(), that lock is now redundant.How can I help you explore Laravel packages today?