aboutcoders/resource-lock-bundle
Symfony bundle providing a resource locking system backed by ORM. Configure a default lock manager and optional custom managers with prefixes, then fetch lock manager services from the container to set, get, and check locks across your app.
Installation:
composer require aboutcoders/resource-lock-bundle:dev-master
Add the bundle to config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):
Abc\Bundle\ResourceLockBundle\AbcResourceLockBundle::class => ['all' => true],
Basic Configuration:
Add to config/packages/aboutcoders_resource_lock.yaml (Symfony 4+):
abc_resource_lock:
db_driver: orm # or 'pdo' for raw PDO
First Use Case: Lock a resource (e.g., inventory item) in a service:
use Abc\Bundle\ResourceLockBundle\Lock\LockManagerInterface;
class InventoryService {
public function __construct(private LockManagerInterface $lockManager) {}
public function updateInventory(string $itemId, int $quantity) {
$lock = $this->lockManager->getLock('inventory_' . $itemId);
if (!$lock->isLocked()) {
$lock->lock(); // Acquire lock
}
// Critical section: Update inventory
$this->update($itemId, $quantity);
$lock->release(); // Release lock
}
}
Locking Strategies:
$lock = $lockManager->getLock('resource_key');
$lock->lock(); // Blocks until lock is acquired
$lock = $lockManager->getLock('resource_key', Lock::SHARED);
Lock Scopes:
inventory_).abc_resource_lock:
managers:
inventory:
prefix: 'inventory_'
Access via:
$inventoryLockManager = $container->get('abc.resource_lock.lock_manager_inventory');
Lock Expiration:
$lock = $lockManager->getLock('resource_key', Lock::TTL_30); // 30-second lock
Retry Logic:
$attempts = 0;
$maxAttempts = 3;
while (!$lock->isLocked() && $attempts < $maxAttempts) {
$lock->lock();
$attempts++;
if (!$lock->isLocked()) {
sleep(2 ** $attempts); // Exponential delay
}
}
Integration with Doctrine:
db_driver: orm to leverage Doctrine for lock storage.Product entity:
$lock = $lockManager->getLock('product_' . $product->getId());
Deadlocks:
A then B in one method, and B then A in another).Lock Leaks:
try-finally or dependency injection for cleanup):
try {
$lock->lock();
// Critical section
} finally {
$lock->release();
}
onKernelTerminate to release locks if the request fails.Driver Limitations:
Configuration Quirks:
db_driver: orm, ensure your Doctrine entity manager is configured.Lock Timeouts:
abc_resource_lock:
default_ttl: 300 # 5 minutes
Check Lock Status:
if ($lock->isLocked()) {
echo "Locked by: " . $lock->getOwner(); // If owner tracking is enabled
}
Log Lock Operations:
Add a decorator to the LockManagerInterface to log lock attempts:
$lockManager->getLock('key')->lock(); // Logs to monolog
Test Lock Contention: Use PHPUnit to simulate concurrent requests:
$this->parallel()->with([1, 2, 3])->run(function ($i) {
$lock = $lockManager->getLock('test_lock');
$lock->lock();
// Assert lock is held
});
Custom Lock Storage:
Extend the LockStorageInterface to support Redis, Memcached, etc.:
class RedisLockStorage implements LockStorageInterface {
// Implement lock logic using Redis
}
Event Listeners: Trigger events on lock acquisition/release:
# config/services.yaml
services:
App\EventListener\LockListener:
tags:
- { name: kernel.event_listener, event: abc.resource_lock.lock_acquired, method: onLockAcquired }
Lock Metadata:
Store additional metadata (e.g., user ID, timestamp) by extending the Lock class:
class ExtendedLock extends Lock {
protected $metadata;
public function setMetadata(array $metadata) {
$this->metadata = $metadata;
}
}
Fallback Mechanisms: Implement a fallback to a simpler lock (e.g., file-based) if the DB driver fails:
try {
$lockManager->getLock('key')->lock();
} catch (LockException $e) {
file_put_contents('/tmp/fallback_lock', 'locked');
}
How can I help you explore Laravel packages today?