Skip to content

Commit e5ddd14

Browse files
committed
feature #22113 [Lock] Include lock component in framework bundle (jderusse)
This PR was squashed before being merged into the 3.4 branch (closes #22113). Discussion ---------- [Lock] Include lock component in framework bundle | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | none | License | MIT | Doc PR | symfony/symfony-docs#8066 ## Usage use the best available "out of box" store (semaphore if available, filesyste otherwise) ```yml framework: lock: ~ # lock: true ``` ```php $this->get('lock')->acquire(); $this->get('lock.factory')->createLock('my resource')->acquire(); ``` use a specific store ```yml framework: lock: flock # lock: semaphore # lock: redis://localhost # lock: "%env(MEMCACHED_DSN)%" # lock: ["%env(REDIS_DSN_1)%", "%env(REDIS_DSN_2)%"] ``` ```php $this->get('lock')->acquire(); $this->get('lock.factory')->createLock('my resource')->acquire(); ``` use a named lock ```yml framework: lock: foo: flock bar: redis://localhost ``` ```php $this->get('lock.foo')->acquire(); $this->get('lock.bar.factory')->createLock('my resource')->acquire(); ``` factory usage ```xml <service id="acme" class="AppBundle\Acme"> <argument type="service"> <service class="Symfony\Component\Lock\Lock"> <factory service="lock.foo.factory" method="createLock" /> <argument>my resource</argument> <argument>30</argument> <!-- optional TTL --> </service> </argument> </service> ``` * [x] Tests Commits ------- b4b00c9 [Lock] Include lock component in framework bundle
2 parents 9ebe218 + b4b00c9 commit e5ddd14

File tree

18 files changed

+618
-5
lines changed

18 files changed

+618
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1919
use Symfony\Component\Config\Definition\ConfigurationInterface;
2020
use Symfony\Component\Form\Form;
21+
use Symfony\Component\Lock\Lock;
22+
use Symfony\Component\Lock\Store\SemaphoreStore;
2123
use Symfony\Component\Serializer\Serializer;
2224
use Symfony\Component\Translation\Translator;
2325
use Symfony\Component\Validator\Validation;
@@ -129,6 +131,7 @@ public function getConfigTreeBuilder()
129131
$this->addCacheSection($rootNode);
130132
$this->addPhpErrorsSection($rootNode);
131133
$this->addWebLinkSection($rootNode);
134+
$this->addLockSection($rootNode);
132135

133136
return $treeBuilder;
134137
}
@@ -875,6 +878,49 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode)
875878
;
876879
}
877880

881+
private function addLockSection(ArrayNodeDefinition $rootNode)
882+
{
883+
$rootNode
884+
->children()
885+
->arrayNode('lock')
886+
->info('Lock configuration')
887+
->{!class_exists(FullStack::class) && class_exists(Lock::class) ? 'canBeDisabled' : 'canBeEnabled'}()
888+
->beforeNormalization()
889+
->ifString()->then(function ($v) { return array('enabled' => true, 'resources' => $v); })
890+
->end()
891+
->beforeNormalization()
892+
->ifTrue(function ($v) { return is_array($v) && !isset($v['resources']); })
893+
->then(function ($v) {
894+
$e = $v['enabled'];
895+
unset($v['enabled']);
896+
897+
return array('enabled' => $e, 'resources' => $v);
898+
})
899+
->end()
900+
->addDefaultsIfNotSet()
901+
->fixXmlConfig('resource')
902+
->children()
903+
->arrayNode('resources')
904+
->requiresAtLeastOneElement()
905+
->defaultValue(array('default' => array(class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock')))
906+
->beforeNormalization()
907+
->ifString()->then(function ($v) { return array('default' => $v); })
908+
->end()
909+
->beforeNormalization()
910+
->ifTrue(function ($v) { return is_array($v) && array_keys($v) === range(0, count($v) - 1); })
911+
->then(function ($v) { return array('default' => $v); })
912+
->end()
913+
->prototype('array')
914+
->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end()
915+
->prototype('scalar')->end()
916+
->end()
917+
->end()
918+
->end()
919+
->end()
920+
->end()
921+
;
922+
}
923+
878924
private function addWebLinkSection(ArrayNodeDefinition $rootNode)
879925
{
880926
$rootNode

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Symfony\Component\DependencyInjection\ContainerInterface;
3939
use Symfony\Component\DependencyInjection\Definition;
4040
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
41+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
4142
use Symfony\Component\DependencyInjection\Exception\LogicException;
4243
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
4344
use Symfony\Component\DependencyInjection\Reference;
@@ -54,6 +55,11 @@
5455
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
5556
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
5657
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
58+
use Symfony\Component\Lock\Factory;
59+
use Symfony\Component\Lock\Lock;
60+
use Symfony\Component\Lock\LockInterface;
61+
use Symfony\Component\Lock\Store\StoreFactory;
62+
use Symfony\Component\Lock\StoreInterface;
5763
use Symfony\Component\PropertyAccess\PropertyAccessor;
5864
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
5965
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
@@ -298,6 +304,10 @@ public function load(array $configs, ContainerBuilder $container)
298304
$this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader);
299305
}
300306

307+
if ($this->isConfigEnabled($container, $config['lock'])) {
308+
$this->registerLockConfiguration($config['lock'], $container, $loader);
309+
}
310+
301311
if ($this->isConfigEnabled($container, $config['web_link'])) {
302312
if (!class_exists(HttpHeaderSerializer::class)) {
303313
throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed.');
@@ -1672,6 +1682,84 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild
16721682
}
16731683
}
16741684

1685+
private function registerLockConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
1686+
{
1687+
$loader->load('lock.xml');
1688+
1689+
foreach ($config['resources'] as $resourceName => $resourceStores) {
1690+
if (0 === count($resourceStores)) {
1691+
continue;
1692+
}
1693+
1694+
// Generate stores
1695+
$storeDefinitions = array();
1696+
foreach ($resourceStores as $storeDsn) {
1697+
$storeDsn = $container->resolveEnvPlaceholders($storeDsn, null, $usedEnvs);
1698+
switch (true) {
1699+
case 'flock' === $storeDsn:
1700+
$storeDefinition = new Reference('lock.store.flock');
1701+
break;
1702+
case 'semaphore' === $storeDsn:
1703+
$storeDefinition = new Reference('lock.store.semaphore');
1704+
break;
1705+
case $usedEnvs || preg_match('#^[a-z]++://#', $storeDsn):
1706+
if (!$container->hasDefinition($connectionDefinitionId = $container->hash($storeDsn))) {
1707+
$connectionDefinition = new Definition(\stdClass::class);
1708+
$connectionDefinition->setPublic(false);
1709+
$connectionDefinition->setFactory(array(StoreFactory::class, 'createConnection'));
1710+
$connectionDefinition->setArguments(array($storeDsn));
1711+
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
1712+
}
1713+
1714+
$storeDefinition = new Definition(StoreInterface::class);
1715+
$storeDefinition->setPublic(false);
1716+
$storeDefinition->setFactory(array(StoreFactory::class, 'createStore'));
1717+
$storeDefinition->setArguments(array(new Reference($connectionDefinitionId)));
1718+
1719+
$container->setDefinition($storeDefinitionId = 'lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
1720+
1721+
$storeDefinition = new Reference($storeDefinitionId);
1722+
break;
1723+
default:
1724+
throw new InvalidArgumentException(sprintf('Lock store DSN "%s" is not valid in resource "%s"', $storeDsn, $resourceName));
1725+
}
1726+
1727+
$storeDefinitions[] = $storeDefinition;
1728+
}
1729+
1730+
// Wrap array of stores with CombinedStore
1731+
if (count($storeDefinitions) > 1) {
1732+
$combinedDefinition = new ChildDefinition('lock.store.combined.abstract');
1733+
$combinedDefinition->replaceArgument(0, $storeDefinitions);
1734+
$container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition);
1735+
} else {
1736+
$container->setAlias('lock.'.$resourceName.'.store', new Alias((string) $storeDefinitions[0], false));
1737+
}
1738+
1739+
// Generate factories for each resource
1740+
$factoryDefinition = new ChildDefinition('lock.factory.abstract');
1741+
$factoryDefinition->replaceArgument(0, new Reference('lock.'.$resourceName.'.store'));
1742+
$container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition);
1743+
1744+
// Generate services for lock instances
1745+
$lockDefinition = new Definition(Lock::class);
1746+
$lockDefinition->setPublic(false);
1747+
$lockDefinition->setFactory(array(new Reference('lock.'.$resourceName.'.factory'), 'createLock'));
1748+
$lockDefinition->setArguments(array($resourceName));
1749+
$container->setDefinition('lock.'.$resourceName, $lockDefinition);
1750+
1751+
// provide alias for default resource
1752+
if ('default' === $resourceName) {
1753+
$container->setAlias('lock.store', new Alias('lock.'.$resourceName.'.store', false));
1754+
$container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false));
1755+
$container->setAlias('lock', new Alias('lock.'.$resourceName, false));
1756+
$container->setAlias(StoreInterface::class, new Alias('lock.store', false));
1757+
$container->setAlias(Factory::class, new Alias('lock.factory', false));
1758+
$container->setAlias(LockInterface::class, new Alias('lock', false));
1759+
}
1760+
}
1761+
}
1762+
16751763
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
16761764
{
16771765
$version = substr(str_replace('/', '-', base64_encode(hash('sha256', uniqid(mt_rand(), true), true))), 0, 22);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<defaults public="false" />
9+
10+
<service id="lock.store.flock" class="Symfony\Component\Lock\Store\FlockStore" />
11+
12+
<service id="lock.store.semaphore" class="Symfony\Component\Lock\Store\SemaphoreStore" />
13+
14+
<service id="lock.store.memcached.abstract" class="Symfony\Component\Lock\Store\MemcachedStore" abstract="true">
15+
<argument /> <!-- Memcached connection service -->
16+
</service>
17+
18+
<service id="lock.store.redis.abstract" class="Symfony\Component\Lock\Store\RedisStore" abstract="true">
19+
<argument /> <!-- Redis connection service -->
20+
</service>
21+
22+
<service id="lock.store.combined.abstract" class="Symfony\Component\Lock\Store\CombinedStore" abstract="true">
23+
<argument /> <!-- List of stores -->
24+
<argument type="service" id="lock.strategy.majority" /> <!-- Strategy -->
25+
</service>
26+
27+
<service id="lock.strategy.majority" class="Symfony\Component\Lock\Strategy\ConsensusStrategy" />
28+
29+
<service id="lock.factory.abstract" class="Symfony\Component\Lock\Factory" abstract="true">
30+
<tag name="monolog.logger" channel="lock" />
31+
<argument /> <!-- Store -->
32+
<call method="setLogger">
33+
<argument type="service" id="logger" on-invalid="ignore" />
34+
</call>
35+
</service>
36+
37+
</services>
38+
</container>

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
3030
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
3131
<xsd:element name="workflow" type="workflow" minOccurs="0" maxOccurs="unbounded" />
32+
<xsd:element name="lock" type="lock" minOccurs="0" maxOccurs="1" />
3233
</xsd:choice>
3334

3435
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -296,4 +297,19 @@
296297
<xsd:enumeration value="workflow" />
297298
</xsd:restriction>
298299
</xsd:simpleType>
300+
301+
<xsd:complexType name="lock">
302+
<xsd:sequence>
303+
<xsd:element name="resource" type="lock_resource" minOccurs="1" maxOccurs="unbounded" />
304+
</xsd:sequence>
305+
<xsd:attribute name="enabled" type="xsd:boolean" />
306+
</xsd:complexType>
307+
308+
<xsd:complexType name="lock_resource">
309+
<xsd:simpleContent>
310+
<xsd:extension base="xsd:string">
311+
<xsd:attribute name="name" type="xsd:string" />
312+
</xsd:extension>
313+
</xsd:simpleContent>
314+
</xsd:complexType>
299315
</xsd:schema>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Bundle\FullStack;
1717
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1818
use Symfony\Component\Config\Definition\Processor;
19+
use Symfony\Component\Lock\Store\SemaphoreStore;
1920

2021
class ConfigurationTest extends TestCase
2122
{
@@ -343,6 +344,14 @@ protected static function getBundleDefaultConfig()
343344
'web_link' => array(
344345
'enabled' => !class_exists(FullStack::class),
345346
),
347+
'lock' => array(
348+
'enabled' => !class_exists(FullStack::class),
349+
'resources' => array(
350+
'default' => array(
351+
class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock',
352+
),
353+
),
354+
),
346355
);
347356
}
348357
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config>
9+
<framework:lock/>
10+
</framework:config>
11+
</container>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
9+
<parameters>
10+
<parameter key="env(REDIS_URL)">redis://paas.com</parameter>
11+
</parameters>
12+
13+
<framework:config>
14+
<framework:lock>
15+
<framework:resource name="foo">semaphore</framework:resource>
16+
<framework:resource name="bar">flock</framework:resource>
17+
<framework:resource name="baz">semaphore</framework:resource>
18+
<framework:resource name="baz">flock</framework:resource>
19+
<framework:resource name="qux">%env(REDIS_URL)%</framework:resource>
20+
</framework:lock>
21+
</framework:config>
22+
</container>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
framework:
2+
lock: ~
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
parameters:
2+
env(REDIS_DSN): redis://paas.com
3+
4+
framework:
5+
lock:
6+
foo: semaphore
7+
bar: flock
8+
baz: [semaphore, flock]
9+
qux: "%env(REDIS_DSN)%"

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"symfony/workflow": "~3.3|~4.0",
5555
"symfony/yaml": "~3.2|~4.0",
5656
"symfony/property-info": "~3.3|~4.0",
57+
"symfony/lock": "~3.4|~4.0",
5758
"symfony/web-link": "~3.3|~4.0",
5859
"doctrine/annotations": "~1.0",
5960
"phpdocumentor/reflection-docblock": "^3.0|^4.0",

src/Symfony/Component/Console/Command/LockableTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private function lock($name = null, $blocking = false)
4646
if (SemaphoreStore::isSupported($blocking)) {
4747
$store = new SemaphoreStore();
4848
} else {
49-
$store = new FlockStore(sys_get_temp_dir());
49+
$store = new FlockStore();
5050
}
5151

5252
$this->lock = (new Factory($store))->createLock($name ?: $this->getName());

src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function testLockReturnsFalseIfAlreadyLockedByAnotherCommand()
4444
if (SemaphoreStore::isSupported(false)) {
4545
$store = new SemaphoreStore();
4646
} else {
47-
$store = new FlockStore(sys_get_temp_dir());
47+
$store = new FlockStore();
4848
}
4949

5050
$lock = (new Factory($store))->createLock($command->getName());

src/Symfony/Component/Lock/Store/FlockStore.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ class FlockStore implements StoreInterface
3232
private $lockPath;
3333

3434
/**
35-
* @param string $lockPath the directory to store the lock
35+
* @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory
3636
*
3737
* @throws LockStorageException If the lock directory could not be created or is not writable
3838
*/
39-
public function __construct($lockPath)
39+
public function __construct($lockPath = null)
4040
{
41+
if (null === $lockPath) {
42+
$lockPath = sys_get_temp_dir();
43+
}
4144
if (!is_dir($lockPath) || !is_writable($lockPath)) {
4245
throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $lockPath));
4346
}

0 commit comments

Comments
 (0)