This PR was squashed before being merged into the 3.3-dev branch (closes #21093).
Discussion
----------
[Lock] Create a lock component
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | they will
| Fixed tickets | #20382
| License | MIT
| Doc PR | symfony/symfony-docs#7364
This PR aim to add a new component Lock going further than the FileSystem\LockHandler by allowing remote backend (like Redis, memcache, etc)
Inspired by
## Usage
The simplest way to use lock is to inject an instance of a Lock in your service
```php
class MyService
{
private $lock;
public function __construct(LockInterface $lock)
{
$this->lock = $lock;
}
public function run()
{
$this->lock->acquire(true);
// If I'm here, no exception had been raised. Lock is acquired
try {
// do my job
} finally {
$this->lock->release();
}
}
}
```
Configured with something like
```yaml
services:
app.my_service:
class: AppBundle\MyService
arguments:
- app.lock.my_service
app.lock.my_service:
class: Symfony\Component\Lock\Lock
factory: ['@locker', createLock]
arguments: ['my_service']
```
If you need to lock serveral resource on runtime, wou'll nneed to inject the LockFactory.
```php
class MyService
{
private $lockFactory;
public function __construct(LockFactoryInterface $lockFactory)
{
$this->lockFactory = $lockFactory;
}
public function run()
{
foreach ($this->items as $item) {
$lock = $this->lockFactory->createLock((string) $item);
try {
$lock->acquire();
} catch (LockConflictedException $e) {
continue;
}
// When I'm here, no exception had been, raised. Lock is acquired
try {
// do my job
} finally {
$lock->release();
}
}
}
}
```
Configured with something like
```yaml
services:
app.my_service:
class: AppBundle\MyService
arguments:
- '@locker'
```
This component allow you to refresh an expirable lock.
This is usefull, if you run a long operation split in several small parts.
If you lock with a ttl for the overall operatoin time and your process crash, the lock will block everybody for the defined TTL.
But thank to the refresh method, you're able to lock for a small TTL, and refresh it between each parts.
```php
class MyService
{
private $lock;
public function __construct(LockInterface $lock)
{
$this->lock = $lock;
}
public function run()
{
$this->lock->acquire(true);
try {
do {
$finished = $this->performLongTask();
// Increase the expire date by 300 more seconds
$this->lock->refresh();
} while (!$finished)
// do my job
} finally {
$this->lock->release();
}
}
}
```
## Naming anc implementation choise
```
$lock->acquire()
vs
$lock->lock()
```
Choose to use acquire, because this component is full of `lock` Symfony\Component\Lock\Lock::Lock` raised a E_TOO_MANY_LOCK in my head.
```
$lock->acquire(false);
$lock->acquire(true);
vs
$lock->aquire()
$lock->waitAndAquire()
```
Not a big fan of flag feature and 2. But I choose to use the blocking flag to offer a simple (and common usecase) implementation
```
$lock = $factory->createLock($key);
$lock->acquire();
vs
$lock->aquire($key)
```
I choose to a the pool of locks implementation. It allow the user to create 2 instances and use cross lock even in the same process.
```
interface LockInterface
final class Lock implements LockInterface
vs
final class Lock
```
I choose to use a Interface even if there is only one implementaiton to offer an extension point here
# TODO
## In this PR
* [x] tests
* [x] add logs
* [x] offer several redis connectors
* [x] try other store implementation to validate the architecture/interface
## In other PR
* documentation
* add configuration in framework bundle
* add stop watch in the debug bar
* improve the combined store (takes the drift into account and elapsed time between each store)
* implement other stores (memcache, ...)
* use this component in session manipulation (fixes #4976)
Commits
-------
018e0fc330
[Lock] Create a lock component
138 lines
5.1 KiB
JSON
138 lines
5.1 KiB
JSON
{
|
|
"name": "symfony/symfony",
|
|
"type": "library",
|
|
"description": "The Symfony PHP framework",
|
|
"keywords": ["framework"],
|
|
"homepage": "https://symfony.com",
|
|
"license": "MIT",
|
|
"authors": [
|
|
{
|
|
"name": "Fabien Potencier",
|
|
"email": "fabien@symfony.com"
|
|
},
|
|
{
|
|
"name": "Symfony Community",
|
|
"homepage": "https://symfony.com/contributors"
|
|
}
|
|
],
|
|
"require": {
|
|
"php": ">=5.5.9",
|
|
"doctrine/common": "~2.4",
|
|
"twig/twig": "~1.28|~2.0",
|
|
"psr/cache": "~1.0",
|
|
"psr/container": "^1.0",
|
|
"psr/log": "~1.0",
|
|
"psr/simple-cache": "^1.0",
|
|
"symfony/polyfill-intl-icu": "~1.0",
|
|
"symfony/polyfill-mbstring": "~1.0",
|
|
"symfony/polyfill-php56": "~1.0",
|
|
"symfony/polyfill-php70": "~1.0",
|
|
"symfony/polyfill-util": "~1.0"
|
|
},
|
|
"replace": {
|
|
"symfony/asset": "self.version",
|
|
"symfony/browser-kit": "self.version",
|
|
"symfony/cache": "self.version",
|
|
"symfony/class-loader": "self.version",
|
|
"symfony/config": "self.version",
|
|
"symfony/console": "self.version",
|
|
"symfony/css-selector": "self.version",
|
|
"symfony/dependency-injection": "self.version",
|
|
"symfony/debug": "self.version",
|
|
"symfony/debug-bundle": "self.version",
|
|
"symfony/doctrine-bridge": "self.version",
|
|
"symfony/dom-crawler": "self.version",
|
|
"symfony/dotenv": "self.version",
|
|
"symfony/event-dispatcher": "self.version",
|
|
"symfony/expression-language": "self.version",
|
|
"symfony/filesystem": "self.version",
|
|
"symfony/finder": "self.version",
|
|
"symfony/form": "self.version",
|
|
"symfony/framework-bundle": "self.version",
|
|
"symfony/http-foundation": "self.version",
|
|
"symfony/http-kernel": "self.version",
|
|
"symfony/inflector": "self.version",
|
|
"symfony/intl": "self.version",
|
|
"symfony/ldap": "self.version",
|
|
"symfony/lock": "self.version",
|
|
"symfony/monolog-bridge": "self.version",
|
|
"symfony/options-resolver": "self.version",
|
|
"symfony/process": "self.version",
|
|
"symfony/property-access": "self.version",
|
|
"symfony/property-info": "self.version",
|
|
"symfony/proxy-manager-bridge": "self.version",
|
|
"symfony/routing": "self.version",
|
|
"symfony/security": "self.version",
|
|
"symfony/security-core": "self.version",
|
|
"symfony/security-csrf": "self.version",
|
|
"symfony/security-guard": "self.version",
|
|
"symfony/security-http": "self.version",
|
|
"symfony/security-bundle": "self.version",
|
|
"symfony/serializer": "self.version",
|
|
"symfony/stopwatch": "self.version",
|
|
"symfony/templating": "self.version",
|
|
"symfony/translation": "self.version",
|
|
"symfony/twig-bridge": "self.version",
|
|
"symfony/twig-bundle": "self.version",
|
|
"symfony/validator": "self.version",
|
|
"symfony/var-dumper": "self.version",
|
|
"symfony/web-profiler-bundle": "self.version",
|
|
"symfony/web-server-bundle": "self.version",
|
|
"symfony/workflow": "self.version",
|
|
"symfony/yaml": "self.version"
|
|
},
|
|
"require-dev": {
|
|
"cache/integration-tests": "dev-master",
|
|
"doctrine/cache": "~1.6",
|
|
"doctrine/data-fixtures": "1.0.*",
|
|
"doctrine/dbal": "~2.4",
|
|
"doctrine/orm": "~2.4,>=2.4.5",
|
|
"doctrine/doctrine-bundle": "~1.4",
|
|
"monolog/monolog": "~1.11",
|
|
"ocramius/proxy-manager": "~0.4|~1.0|~2.0",
|
|
"predis/predis": "~1.0",
|
|
"egulias/email-validator": "~1.2,>=1.2.8|~2.0",
|
|
"symfony/phpunit-bridge": "~3.2",
|
|
"symfony/polyfill-apcu": "~1.1",
|
|
"symfony/security-acl": "~2.8|~3.0",
|
|
"phpdocumentor/reflection-docblock": "^3.0",
|
|
"sensio/framework-extra-bundle": "^3.0.2"
|
|
},
|
|
"conflict": {
|
|
"phpdocumentor/reflection-docblock": "<3.0",
|
|
"phpdocumentor/type-resolver": "<0.2.0",
|
|
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
|
|
},
|
|
"provide": {
|
|
"psr/cache-implementation": "1.0",
|
|
"psr/container-implementation": "1.0",
|
|
"psr/simple-cache-implementation": "1.0"
|
|
},
|
|
"autoload": {
|
|
"psr-4": {
|
|
"Symfony\\Bridge\\Doctrine\\": "src/Symfony/Bridge/Doctrine/",
|
|
"Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/",
|
|
"Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/",
|
|
"Symfony\\Bridge\\Swiftmailer\\": "src/Symfony/Bridge/Swiftmailer/",
|
|
"Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/",
|
|
"Symfony\\Bundle\\": "src/Symfony/Bundle/",
|
|
"Symfony\\Component\\": "src/Symfony/Component/"
|
|
},
|
|
"classmap": [
|
|
"src/Symfony/Component/Intl/Resources/stubs"
|
|
],
|
|
"exclude-from-classmap": [
|
|
"**/Tests/"
|
|
]
|
|
},
|
|
"autoload-dev": {
|
|
"files": [ "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ]
|
|
},
|
|
"minimum-stability": "dev",
|
|
"extra": {
|
|
"branch-alias": {
|
|
"dev-master": "3.3-dev"
|
|
}
|
|
}
|
|
}
|