Skip to content

Commit ce3564b

Browse files
feature #44155 [FrameworkBundle] Add semaphore configuration (jderusse)
This PR was merged into the 6.1 branch. Discussion ---------- [FrameworkBundle] Add semaphore configuration | Q | A | ------------- | --- | Branch? | 6.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | TODO Adds a new configuration for semaphores ``` # see https://symfony.com/doc/current/reference/configuration/framework.html framework: semaphore: redis://localhost semaphore: foo: redis://localhost/1 bar: redis://localhost/2 ``` Commits ------- 8dbd850 [FrameworkBundle] Add semaphore configuration
2 parents 9cbc853 + 8dbd850 commit ce3564b

File tree

10 files changed

+215
-0
lines changed

10 files changed

+215
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.1
55
---
66

7+
* Add support for configuring semaphores
78
* Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set.
89
* Load PHP configuration files by default in the `MicroKernelTrait`
910
* Add `cache:pool:invalidate-tags` command

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\PropertyAccess\PropertyAccessor;
3636
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
3737
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
38+
use Symfony\Component\Semaphore\Semaphore;
3839
use Symfony\Component\Serializer\Serializer;
3940
use Symfony\Component\Translation\Translator;
4041
use Symfony\Component\Uid\Factory\UuidFactory;
@@ -157,6 +158,7 @@ public function getConfigTreeBuilder(): TreeBuilder
157158
$this->addExceptionsSection($rootNode);
158159
$this->addWebLinkSection($rootNode, $enableIfStandalone);
159160
$this->addLockSection($rootNode, $enableIfStandalone);
161+
$this->addSemaphoreSection($rootNode, $enableIfStandalone);
160162
$this->addMessengerSection($rootNode, $enableIfStandalone);
161163
$this->addRobotsIndexSection($rootNode);
162164
$this->addHttpClientSection($rootNode, $enableIfStandalone);
@@ -1282,6 +1284,61 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI
12821284
;
12831285
}
12841286

1287+
private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
1288+
{
1289+
$rootNode
1290+
->children()
1291+
->arrayNode('semaphore')
1292+
->info('Semaphore configuration')
1293+
->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}()
1294+
->beforeNormalization()
1295+
->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; })
1296+
->end()
1297+
->beforeNormalization()
1298+
->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); })
1299+
->then(function ($v) { return $v + ['enabled' => true]; })
1300+
->end()
1301+
->beforeNormalization()
1302+
->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); })
1303+
->then(function ($v) {
1304+
$e = $v['enabled'];
1305+
unset($v['enabled']);
1306+
1307+
return ['enabled' => $e, 'resources' => $v];
1308+
})
1309+
->end()
1310+
->addDefaultsIfNotSet()
1311+
->fixXmlConfig('resource')
1312+
->children()
1313+
->arrayNode('resources')
1314+
->normalizeKeys(false)
1315+
->useAttributeAsKey('name')
1316+
->requiresAtLeastOneElement()
1317+
->beforeNormalization()
1318+
->ifString()->then(function ($v) { return ['default' => $v]; })
1319+
->end()
1320+
->beforeNormalization()
1321+
->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); })
1322+
->then(function ($v) {
1323+
$resources = [];
1324+
foreach ($v as $resource) {
1325+
$resources[] = \is_array($resource) && isset($resource['name'])
1326+
? [$resource['name'] => $resource['value']]
1327+
: ['default' => $resource]
1328+
;
1329+
}
1330+
1331+
return array_merge_recursive([], ...$resources);
1332+
})
1333+
->end()
1334+
->prototype('scalar')->end()
1335+
->end()
1336+
->end()
1337+
->end()
1338+
->end()
1339+
;
1340+
}
1341+
12851342
private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
12861343
{
12871344
$rootNode

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@
185185
use Symfony\Component\Security\Core\Exception\AuthenticationException;
186186
use Symfony\Component\Security\Core\Security;
187187
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
188+
use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface;
189+
use Symfony\Component\Semaphore\Semaphore;
190+
use Symfony\Component\Semaphore\SemaphoreFactory;
191+
use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory;
188192
use Symfony\Component\Serializer\Encoder\DecoderInterface;
189193
use Symfony\Component\Serializer\Encoder\EncoderInterface;
190194
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
@@ -404,6 +408,10 @@ public function load(array $configs, ContainerBuilder $container)
404408
$this->registerLockConfiguration($config['lock'], $container, $loader);
405409
}
406410

411+
if ($this->isConfigEnabled($container, $config['semaphore'])) {
412+
$this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader);
413+
}
414+
407415
if ($this->isConfigEnabled($container, $config['rate_limiter'])) {
408416
if (!interface_exists(LimiterInterface::class)) {
409417
throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".');
@@ -1896,6 +1904,39 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont
18961904
}
18971905
}
18981906

1907+
private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
1908+
{
1909+
$loader->load('semaphore.php');
1910+
1911+
foreach ($config['resources'] as $resourceName => $resourceStore) {
1912+
$storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs);
1913+
$storeDefinition = new Definition(SemaphoreStoreInterface::class);
1914+
$storeDefinition->setFactory([SemaphoreStoreFactory::class, 'createStore']);
1915+
$storeDefinition->setArguments([$resourceStore]);
1916+
1917+
$container->setDefinition($storeDefinitionId = '.semaphore.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
1918+
1919+
// Generate factories for each resource
1920+
$factoryDefinition = new ChildDefinition('semaphore.factory.abstract');
1921+
$factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId));
1922+
$container->setDefinition('semaphore.'.$resourceName.'.factory', $factoryDefinition);
1923+
1924+
// Generate services for semaphore instances
1925+
$semaphoreDefinition = new Definition(Semaphore::class);
1926+
$semaphoreDefinition->setPublic(false);
1927+
$semaphoreDefinition->setFactory([new Reference('semaphore.'.$resourceName.'.factory'), 'createSemaphore']);
1928+
$semaphoreDefinition->setArguments([$resourceName]);
1929+
1930+
// provide alias for default resource
1931+
if ('default' === $resourceName) {
1932+
$container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false));
1933+
$container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false));
1934+
} else {
1935+
$container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory');
1936+
}
1937+
}
1938+
}
1939+
18991940
private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig)
19001941
{
19011942
if (!interface_exists(MessageBusInterface::class)) {

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
@@ -31,6 +31,7 @@
3131
<xsd:element name="php-errors" type="php-errors" minOccurs="0" maxOccurs="1" />
3232
<xsd:element name="exceptions" type="exceptions" minOccurs="0" maxOccurs="1" />
3333
<xsd:element name="lock" type="lock" minOccurs="0" maxOccurs="1" />
34+
<xsd:element name="semaphore" type="semaphore" minOccurs="0" maxOccurs="1" />
3435
<xsd:element name="messenger" type="messenger" minOccurs="0" maxOccurs="1" />
3536
<xsd:element name="http-client" type="http_client" minOccurs="0" maxOccurs="1" />
3637
<xsd:element name="mailer" type="mailer" minOccurs="0" maxOccurs="1" />
@@ -482,6 +483,21 @@
482483
</xsd:simpleContent>
483484
</xsd:complexType>
484485

486+
<xsd:complexType name="semaphore">
487+
<xsd:sequence>
488+
<xsd:element name="resource" type="semaphore_resource" minOccurs="1" maxOccurs="unbounded" />
489+
</xsd:sequence>
490+
<xsd:attribute name="enabled" type="xsd:boolean" />
491+
</xsd:complexType>
492+
493+
<xsd:complexType name="semaphore_resource">
494+
<xsd:simpleContent>
495+
<xsd:extension base="xsd:string">
496+
<xsd:attribute name="name" type="xsd:string" />
497+
</xsd:extension>
498+
</xsd:simpleContent>
499+
</xsd:complexType>
500+
485501
<xsd:complexType name="messenger">
486502
<xsd:sequence>
487503
<xsd:element name="serializer" type="messenger_serializer" minOccurs="0" />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\Semaphore\SemaphoreFactory;
15+
16+
return static function (ContainerConfigurator $container) {
17+
$container->services()
18+
->set('semaphore.factory.abstract', SemaphoreFactory::class)->abstract()
19+
->args([abstract_arg('Store')])
20+
->call('setLogger', [service('logger')->ignoreOnInvalid()])
21+
->tag('monolog.logger', ['channel' => 'semaphore'])
22+
;
23+
};

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,57 @@ public function testLockMergeConfigs()
265265
);
266266
}
267267

268+
/**
269+
* @dataProvider provideValidSemaphoreConfigurationTests
270+
*/
271+
public function testValidSemaphoreConfiguration($semaphoreConfig, $processedConfig)
272+
{
273+
$processor = new Processor();
274+
$configuration = new Configuration(true);
275+
$config = $processor->processConfiguration($configuration, [
276+
[
277+
'semaphore' => $semaphoreConfig,
278+
],
279+
]);
280+
281+
$this->assertArrayHasKey('semaphore', $config);
282+
283+
$this->assertEquals($processedConfig, $config['semaphore']);
284+
}
285+
286+
public function provideValidSemaphoreConfigurationTests()
287+
{
288+
yield [null, ['enabled' => true, 'resources' => []]];
289+
290+
yield ['redis://default', ['enabled' => true, 'resources' => ['default' => 'redis://default']]];
291+
yield [['foo' => 'redis://foo', 'bar' => 'redis://bar'], ['enabled' => true, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]];
292+
yield [['default' => 'redis://default'], ['enabled' => true, 'resources' => ['default' => 'redis://default']]];
293+
294+
yield [['enabled' => false, 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]];
295+
yield [['enabled' => false, 'foo' => 'redis://foo', 'bar' => 'redis://bar'], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]];
296+
yield [['enabled' => false, 'default' => 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]];
297+
298+
yield [['resources' => 'redis://default'], ['enabled' => true, 'resources' => ['default' => 'redis://default']]];
299+
yield [['resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']], ['enabled' => true, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]];
300+
yield [['resources' => ['default' => 'redis://default']], ['enabled' => true, 'resources' => ['default' => 'redis://default']]];
301+
302+
yield [['enabled' => false, 'resources' => 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]];
303+
yield [['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]];
304+
yield [['enabled' => false, 'resources' => ['default' => 'redis://default']], ['enabled' => false, 'resources' => ['default' => 'redis://default']]];
305+
306+
// xml
307+
308+
yield [['resource' => ['redis://default']], ['enabled' => true, 'resources' => ['default' => 'redis://default']]];
309+
yield [['resource' => ['redis://default', ['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['default' => 'redis://default', 'foo' => 'redis://default']]];
310+
yield [['resource' => [['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['foo' => 'redis://default']]];
311+
yield [['resource' => [['name' => 'foo', 'value' => 'redis://default'], ['name' => 'bar', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['foo' => 'redis://default', 'bar' => 'redis://default']]];
312+
313+
yield [['enabled' => false, 'resource' => ['redis://default']], ['enabled' => false, 'resources' => ['default' => 'redis://default']]];
314+
yield [['enabled' => false, 'resource' => ['redis://default', ['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => false, 'resources' => ['default' => 'redis://default', 'foo' => 'redis://default']]];
315+
yield [['enabled' => false, 'resource' => [['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => false, 'resources' => ['foo' => 'redis://default']]];
316+
yield [['enabled' => false, 'resource' => [['name' => 'foo', 'value' => 'redis://foo'], ['name' => 'bar', 'value' => 'redis://bar']]], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]];
317+
}
318+
268319
public function testItShowANiceMessageIfTwoMessengerBusesAreConfiguredButNoDefaultBus()
269320
{
270321
$expectedMessage = 'You must specify the "default_bus" if you define more than one bus.';
@@ -525,6 +576,11 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
525576
],
526577
],
527578
],
579+
'semaphore' => [
580+
'enabled' => !class_exists(FullStack::class),
581+
'resources' => [
582+
],
583+
],
528584
'messenger' => [
529585
'enabled' => !class_exists(FullStack::class) && interface_exists(MessageBusInterface::class),
530586
'routing' => [],
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 https://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config>
9+
<framework:semaphore/>
10+
</framework:config>
11+
</container>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
framework:
2+
semaphore: redis://localhost
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
env(REDIS_DSN): redis://paas.com
3+
4+
framework:
5+
semaphore:
6+
foo: redis://paas.com
7+
qux: "%env(REDIS_DSN)%"

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"symfony/process": "^5.4|^6.0",
5454
"symfony/rate-limiter": "^5.4|^6.0",
5555
"symfony/security-bundle": "^5.4|^6.0",
56+
"symfony/semaphore": "^5.4|^6.0",
5657
"symfony/serializer": "^5.4|^6.0",
5758
"symfony/stopwatch": "^5.4|^6.0",
5859
"symfony/string": "^5.4|^6.0",

0 commit comments

Comments
 (0)