symfony/lock
Symfony Lock component provides a unified API to create and manage locks, ensuring exclusive access to shared resources. Supports multiple backends (e.g., filesystem, Redis, PDO) to prevent race conditions in concurrent PHP apps.
Installation:
composer require symfony/lock
For Laravel, use the symfony/lock facade directly or integrate with Laravel’s built-in Lock facade (v8.0+).
Basic Usage:
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
// Create a lock factory with a FlockStore (file-based)
$factory = new LockFactory(new FlockStore('/tmp/locks/'));
// Acquire a lock with a 10-second TTL
$lock = $factory->createLock('order_123', 10);
$acquired = $lock->acquire(true); // Block until acquired
if ($acquired) {
// Critical section
$lock->release(); // Release when done
}
Laravel Integration:
use Illuminate\Support\Facades\Lock;
// Use Laravel's built-in facade (v8.0+)
Lock::blocking('order_123', 10, function () {
// Critical section
});
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\RedisStore;
$factory = new LockFactory(new RedisStore('redis://localhost'));
$lock = $factory->createLock('process_payment_123', 300); // 5-minute TTL
if ($lock->acquire()) {
try {
// Process payment logic
} finally {
$lock->release();
}
}
$lock->acquire(true); // Blocks until acquired
$lock->acquire(true, 5); // Blocks for 5 seconds max
if ($lock->acquire(false)) {
// Critical section
}
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\RedisStore;
class ProcessPayment implements ShouldQueue
{
protected $factory;
public function __construct()
{
$this->factory = new LockFactory(new RedisStore('redis://localhost'));
}
public function handle()
{
$lock = $this->factory->createLock('payment_' . $this->paymentId, 300);
if ($lock->acquire()) {
try {
// Process payment
} finally {
$lock->release();
}
}
}
}
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\PdoStore;
$factory = new LockFactory(new PdoStore($pdo, 'locks'));
$lock = $factory->createLock('retryable_job_456', 60);
if ($lock->acquire(false)) {
// Proceed if lock acquired
} else {
// Fallback or retry logic
$this->retryAfterDelay();
}
use Symfony\Component\Lock\Store\RedisStore;
$store = new RedisStore('redis://redis-cluster:6379');
$factory = new LockFactory($store);
// Use with Laravel's Lock facade
Lock::extend('redis', function () use ($factory) {
return $factory->createLock('key', 10);
});
Lock::blocking('redis', 'order_789', 10, function () {
// Critical section
});
Register the lock factory as a singleton:
$app->singleton(LockFactory::class, function ($app) {
return new LockFactory(new RedisStore($app['redis']->connection()));
});
Extend AbstractStore for domain-specific needs:
use Symfony\Component\Lock\Store\AbstractStore;
class DynamoDbStore extends AbstractStore
{
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
protected function doAcquire($name, $ttl, $block, $timeout)
{
// Custom DynamoDB logic
}
// Implement other required methods
}
Use LockKeyNormalizer for consistent key generation:
use Symfony\Component\Lock\LockKeyNormalizer;
$normalizer = new LockKeyNormalizer();
$normalizedKey = $normalizer->normalize('user:123:profile');
Lock Leaks:
finally blocks or Laravel’s Lock::blocking() with automatic release.Lock::blocking('key', 10, function () {
// Automatically released even if exception occurs
});
Network Partitions:
Key Collisions:
user:123 vs. user/123).LockKeyNormalizer for consistent key generation.PostgreSQL Transaction Contention:
PdoStore with LOCK TABLE syntax.Redis Cluster Limitations:
RedisStore may fail in clusters if the lock key is not hash-tagged.{sha}:key format or enable Redis Cluster support in the store.Check Lock Status:
if (!$lock->isAcquired()) {
log("Lock not acquired for key: {$lock->getName()}");
}
Inspect Store State:
RedisStore, use redis-cli to check keys:
redis-cli keys '*lock*'
PdoStore, query the locks table directly.Log Lock Operations:
$factory->createLock('key', 10)->acquire(true, 5, function ($waited) {
log("Acquired lock after {$waited}ms");
});
FlockStore Paths:
$store = new FlockStore('/var/lock/app');
if (!file_exists($store->getDirectory())) {
mkdir($store->getDirectory(), 0755, true);
}
Redis Connection:
$store = new RedisStore('redis://default,redis://backup');
PDO Store Schema:
PdoStore, ensure the locks table exists:
CREATE TABLE locks (
name VARCHAR(255) PRIMARY KEY,
expires_at DATETIME NOT NULL
);
Custom Stores:
StoreInterface for domain-specific backends (e.g., etcd, consul).class EtcdStore implements StoreInterface
{
public function acquire($name, $ttl, $block, $timeout, callable $callback)
{
// Custom etcd logic
}
// Implement other required methods
}
Lock Factory Extensions:
LockFactory to add domain-specific lock creation:
class DomainLockFactory extends LockFactory
{
public function createOrderLock($orderId, $ttl = 300)
{
return $this->createLock("order:{$orderId}", $ttl);
}
}
Event Listeners:
LockAcquiredEvent):
$factory->createLock('key')->acquire(true, 5, function ($waited) {
event(new LockAcquiredEvent($this->getName(), $waited));
});
Laravel Events:
Lock::blocking('key', 10, function () {
event(new OrderProcessed($order));
});
How can I help you explore Laravel packages today?