Locking
class Cake\Lock\Lock
Locking helps you coordinate access to shared resources across concurrent requests, CLI commands, queue workers, or background jobs. Use locks when you need to ensure that only one process performs a critical section at a time.
CakePHP provides the Cake\Lock\Lock facade together with pluggable lock engines for Redis, Memcached, local files, and no-op/testing usage.
Configuring Lock Engines
Lock engine configurations are typically defined in config/app.php:
'Lock' => [
'default' => [
'className' => 'Redis',
'host' => '127.0.0.1',
'port' => 6379,
'prefix' => 'myapp_lock_',
'ttl' => 60,
],
],You can also configure lock engines at runtime:
use Cake\Lock\Lock;
Lock::setConfig('orders', [
'className' => 'Redis',
'host' => '127.0.0.1',
'port' => 6379,
'prefix' => 'orders_',
'ttl' => 30,
]);Lock configurations use the same className conventions as other registry-based CakePHP services:
Lock::setConfig('default', ['className' => 'Redis']);
Lock::setConfig('default', ['className' => 'Cake\Lock\Engine\RedisLockEngine']);If an engine cannot be initialized, CakePHP falls back to the noop NullLockEngine and emits a warning.
Acquiring and Releasing Locks
Use Lock::acquire() to attempt a non-blocking lock:
use Cake\Lock\Lock;
$lock = Lock::acquire('invoice-' . $invoiceId, ttl: 60);
if ($lock === null) {
return;
}
try {
$this->Invoices->send($invoiceId);
} finally {
$lock->release();
}The returned AcquiredLock object represents the held lock. You can:
- call
release()to release it explicitly - call
refresh()to extend the TTL for long-running work - rely on its best-effort destructor cleanup if the handle is dropped
Explicit release is still recommended for predictable behavior.
Preferred Usage with synchronized()
In many cases, Lock::synchronized() is the simplest and safest API because it guarantees prompt release:
$result = Lock::synchronized(
'reports-daily',
function () {
return $this->Reports->buildDaily();
},
ttl: 120,
timeout: 10,
);If the lock cannot be acquired before the timeout expires, synchronized() returns null.
Blocking Acquisition
Use Lock::acquireBlocking() when you want to wait for a lock to become available:
$lock = Lock::acquireBlocking(
'payment-' . $paymentId,
ttl: 60,
timeout: 10,
retryInterval: 100,
);
if ($lock === null) {
return;
}
try {
$this->Payments->capture($paymentId);
} finally {
$lock->release();
}The retryInterval value is expressed in milliseconds.
Inspecting and Managing Locks
CakePHP provides additional helper methods for lock lifecycle management:
if (Lock::isLocked('imports-products')) {
return;
}
$lock = Lock::acquire('imports-products');
if ($lock) {
$lock->refresh(120);
$lock->release();
}
Lock::forceRelease('imports-products');isLocked()performs a point-in-time check using the underlying enginerefresh()extends the lock TTL if the current owner still holds the lockforceRelease()bypasses ownership checks and should only be used for administrative recovery flows
Available Engines
CakePHP ships with the following lock engines:
RedisRecommended for distributed systems and multi-node deploymentsMemcachedSuitable when Memcached is already part of your infrastructureFileUseful for single-server deployments using local filesystem locksNullUseful for tests, local development, and intentional no-op behavior
Shared Engine Options
All lock engines support these common options:
prefixPrefix added to lock keysttlDefault lock time-to-live in seconds
RedisLockEngine Options
hostRedis server hostportRedis server portpasswordRedis passworddatabaseRedis database indextimeoutConnection timeoutpersistentWhether to use persistent connections
MemcachedLockEngine Options
serversArray of Memcached serverspersistentPersistent connection identifier
FileLockEngine Options
pathDirectory used to store lock files
The FileLockEngine is local to a single host. Unlike Redis or Memcached, it does not provide true TTL-based expiration in the backend. Locks are normally released explicitly, when the lock handle is destroyed, or when the process terminates.