Skip to content

Commit a30f387

Browse files
committed
feature symfony#51273 [Config] Add ArrayNodeDefinition::acceptAndWrap() to list alternative types that should be accepted and wrapped in an array (nicolas-grekas)
This PR was merged into the 7.4 branch. Discussion ---------- [Config] Add `ArrayNodeDefinition::acceptAndWrap()` to list alternative types that should be accepted and wrapped in an array | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - With symfony#58450, we lost some type info when generating config builders, by preventing symfony#44166 to work correctly. This PR improves the description of config trees by adding an `ArrayNodeDefinition::acceptAndWrap()` method that allows one to declare which alternative types should be accepted at array-node levels. This declaration then triggers the wrapping of such cases into arrays, and also hints the config builder generator so that it can generate more accurate types for configurator methods. Commits ------- a0d0f95 [Config] Add `ArrayNodeDefinition::acceptAndWrap()` to list alternative types that should be accepted and wrapped in an array
2 parents a8749a3 + a0d0f95 commit a30f387

File tree

17 files changed

+286
-305
lines changed

17 files changed

+286
-305
lines changed

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

Lines changed: 173 additions & 176 deletions
Large diffs are not rendered by default.

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,10 +2291,6 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont
22912291
// Generate stores
22922292
$storeDefinitions = [];
22932293
foreach ($resourceStores as $resourceStore) {
2294-
if (null === $resourceStore) {
2295-
$resourceStore = 'null';
2296-
}
2297-
22982294
$storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs);
22992295
if (!$usedEnvs && !str_contains($resourceStore, ':') && !\in_array($resourceStore, ['flock', 'semaphore', 'in-memory', 'null'], true)) {
23002296
$resourceStore = new Reference($resourceStore);

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

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -72,36 +72,6 @@ public function testAssetPackageCannotHavePathAndUrl()
7272
});
7373
}
7474

75-
public function testWorkflowValidationPlacesIsArray()
76-
{
77-
$this->expectException(InvalidConfigurationException::class);
78-
$this->expectExceptionMessage('The "places" option must be an array or a "FQCN::glob" pattern in workflow configuration.');
79-
$this->createContainerFromClosure(function ($container) {
80-
$container->loadFromExtension('framework', [
81-
'workflows' => [
82-
'article' => [
83-
'places' => null,
84-
],
85-
],
86-
]);
87-
});
88-
}
89-
90-
public function testWorkflowValidationTransitonsIsArray()
91-
{
92-
$this->expectException(InvalidConfigurationException::class);
93-
$this->expectExceptionMessage('The "transitions" option must be an array in workflow configuration.');
94-
$this->createContainerFromClosure(function ($container) {
95-
$container->loadFromExtension('framework', [
96-
'workflows' => [
97-
'article' => [
98-
'transitions' => null,
99-
],
100-
],
101-
]);
102-
});
103-
}
104-
10575
public function testWorkflowValidationStateMachine()
10676
{
10777
$this->expectException(InvalidDefinitionException::class);

src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public function getConfigTreeBuilder(): TreeBuilder
5757
$rootNode
5858
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/security.html', 'symfony/security-bundle')
5959
->beforeNormalization()
60-
->always()
61-
->then(function ($v) {
60+
->ifArray()
61+
->then(static function ($v) {
6262
if (isset($v['hide_user_not_found']) && isset($v['expose_security_errors'])) {
6363
throw new InvalidConfigurationException('You cannot use both "hide_user_not_found" and "expose_security_errors" at the same time.');
6464
}
@@ -80,7 +80,7 @@ public function getConfigTreeBuilder(): TreeBuilder
8080
->setDeprecated('symfony/security-bundle', '7.3', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "expose_security_errors" option instead.')
8181
->end()
8282
->enumNode('expose_security_errors')
83-
->beforeNormalization()->ifString()->then(fn ($v) => ExposeSecurityLevel::tryFrom($v))->end()
83+
->beforeNormalization()->ifString()->then(static fn ($v) => ExposeSecurityLevel::tryFrom($v))->end()
8484
->values(ExposeSecurityLevel::cases())
8585
->defaultValue(ExposeSecurityLevel::None)
8686
->end()
@@ -129,11 +129,7 @@ private function addRoleHierarchySection(ArrayNodeDefinition $rootNode): void
129129
->useAttributeAsKey('id')
130130
->prototype('array')
131131
->performNoDeepMerging()
132-
->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end()
133-
->beforeNormalization()
134-
->ifTrue(fn ($v) => \is_array($v) && isset($v['value']))
135-
->then(fn ($v) => preg_split('/\s*,\s*/', $v['value']))
136-
->end()
132+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
137133
->prototype('scalar')->end()
138134
->end()
139135
->end()
@@ -159,7 +155,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode): void
159155
->scalarNode('host')->defaultNull()->end()
160156
->integerNode('port')->defaultNull()->end()
161157
->arrayNode('ips', 'ip')
162-
->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end()
158+
->acceptAndWrap(['string'])
163159
->prototype('scalar')->end()
164160
->end()
165161
->arrayNode('attributes', 'attribute')
@@ -168,14 +164,14 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode): void
168164
->end()
169165
->scalarNode('route')->defaultNull()->end()
170166
->arrayNode('methods', 'method')
171-
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
167+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
172168
->prototype('scalar')->end()
173169
->end()
174170
->scalarNode('allow_if')->defaultNull()->end()
175171
->end()
176172
->children()
177173
->arrayNode('roles', 'role')
178-
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
174+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
179175
->prototype('scalar')->end()
180176
->end()
181177
->end()
@@ -205,12 +201,12 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
205201
->scalarNode('pattern')
206202
->beforeNormalization()
207203
->ifArray()
208-
->then(fn ($v) => \sprintf('(?:%s)', implode('|', $v)))
204+
->then(static fn ($v) => \sprintf('(?:%s)', implode('|', $v)))
209205
->end()
210206
->end()
211207
->scalarNode('host')->end()
212208
->arrayNode('methods')
213-
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
209+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
214210
->prototype('scalar')->end()
215211
->end()
216212
->booleanNode('security')->defaultTrue()->end()
@@ -233,11 +229,11 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
233229
->treatTrueLike([])
234230
->canBeUnset()
235231
->beforeNormalization()
236-
->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf'])))
237-
->then(function (array $v): array {
232+
->ifArray()
233+
->then(static function ($v) {
238234
if (isset($v['csrf_token_manager'])) {
239-
$v['enable_csrf'] = true;
240-
} elseif ($v['enable_csrf']) {
235+
$v['enable_csrf'] ??= true;
236+
} elseif ($v['enable_csrf'] ?? false) {
241237
$v['csrf_token_manager'] = 'security.csrf.token_manager';
242238
}
243239

@@ -254,7 +250,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
254250
->booleanNode('invalidate_session')->defaultTrue()->end()
255251
->arrayNode('clear_site_data')
256252
->performNoDeepMerging()
257-
->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end()
253+
->beforeNormalization()->ifString()->then(static fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end()
258254
->enumPrototype()
259255
->values([
260256
'*', 'cache', 'cookies', 'storage', 'executionContexts',
@@ -265,9 +261,10 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
265261
->children()
266262
->arrayNode('delete_cookies', 'delete_cookie')
267263
->normalizeKeys(false)
264+
->acceptAndWrap(['string'])
268265
->beforeNormalization()
269-
->ifTrue(fn ($v) => \is_array($v) && \is_int(key($v)))
270-
->then(fn ($v) => array_map(fn ($v) => ['name' => $v], $v))
266+
->ifArray()
267+
->then(static fn ($v) => array_map(static fn ($v) => \is_string($v) ? ['name' => $v] : $v, $v))
271268
->end()
272269
->useAttributeAsKey('name')
273270
->prototype('array')
@@ -379,10 +376,7 @@ private function addProvidersSection(ArrayNodeDefinition $rootNode): void
379376
->arrayNode('chain')
380377
->children()
381378
->arrayNode('providers', 'provider')
382-
->beforeNormalization()
383-
->ifString()
384-
->then(fn ($v) => preg_split('/\s*,\s*/', $v))
385-
->end()
379+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
386380
->prototype('scalar')->end()
387381
->end()
388382
->end()
@@ -427,7 +421,7 @@ private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void
427421
->prototype('array')
428422
->canBeUnset()
429423
->performNoDeepMerging()
430-
->beforeNormalization()->ifString()->then(fn ($v) => ['algorithm' => $v])->end()
424+
->acceptAndWrap(['string'], 'algorithm')
431425
->children()
432426
->scalarNode('algorithm')
433427
->cannotBeEmpty()
@@ -437,8 +431,8 @@ private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void
437431
->end()
438432
->end()
439433
->arrayNode('migrate_from')
434+
->acceptAndWrap(['string'])
440435
->prototype('scalar')->end()
441-
->beforeNormalization()->castToArray()->end()
442436
->end()
443437
->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()
444438
->scalarNode('key_length')->defaultValue(40)->end()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,24 +99,28 @@ public function addConfiguration(NodeBuilder $node): void
9999
->thenInvalid('You must set either "discovery" or "key" or "keyset".')
100100
->end()
101101
->beforeNormalization()
102-
->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm']))
102+
->ifArray()
103103
->then(static function ($v) {
104-
if (isset($v['algorithms'])) {
104+
if (isset($v['algorithms']) && isset($v['algorithm'])) {
105105
throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.');
106106
}
107-
$v['algorithms'] = [$v['algorithm']];
108-
unset($v['algorithm']);
107+
if (\is_string($v['algorithm'] ?? null)) {
108+
$v['algorithms'] = [$v['algorithm']];
109+
unset($v['algorithm']);
110+
}
109111

110112
return $v;
111113
})
112114
->end()
113115
->beforeNormalization()
114-
->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key']))
116+
->ifArray()
115117
->then(static function ($v) {
116-
if (isset($v['keyset'])) {
118+
if (isset($v['keyset']) && isset($v['key'])) {
117119
throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.');
118120
}
119-
$v['keyset'] = \sprintf('{"keys":[%s]}', $v['key']);
121+
if (\is_string($v['key'] ?? null)) {
122+
$v['keyset'] = \sprintf('{"keys":[%s]}', $v['key']);
123+
}
120124

121125
return $v;
122126
})

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function addConfiguration(NodeBuilder $node): void
6565
->arrayNode($this->getKey())
6666
->beforeNormalization()
6767
->ifString()
68-
->then(fn ($v) => ['claim' => 'sub', 'base_uri' => $v])
68+
->then(static fn ($v) => ['claim' => 'sub', 'base_uri' => $v])
6969
->end()
7070
->children()
7171
->scalarNode('base_uri')

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,39 +47,28 @@ public function addConfiguration(NodeDefinition $node): void
4747
$builder
4848
->scalarNode('realm')->defaultNull()->end()
4949
->arrayNode('token_extractors', 'token_extractor')
50-
->beforeNormalization()
51-
->ifString()
52-
->then(fn ($v) => [$v])
53-
->end()
50+
->acceptAndWrap(['string'])
5451
->cannotBeEmpty()
55-
->defaultValue([
56-
'security.access_token_extractor.header',
57-
])
52+
->defaultValue(['security.access_token_extractor.header'])
5853
->scalarPrototype()->end()
5954
->end()
6055
;
6156

6257
$tokenHandlerNodeBuilder = $builder
6358
->arrayNode('token_handler')
64-
->example([
65-
'id' => 'App\Security\CustomTokenHandler',
66-
])
67-
68-
->beforeNormalization()
69-
->ifString()
70-
->then(fn ($v) => ['id' => $v])
71-
->end()
59+
->example(['id' => 'App\Security\CustomTokenHandler'])
60+
->acceptAndWrap(['string'], 'id')
7261

73-
->beforeNormalization()
74-
->ifTrue(fn ($v) => \is_array($v) && 1 < \count($v))
75-
->then(fn () => throw new InvalidConfigurationException('You cannot configure multiple token handlers.'))
62+
->validate()
63+
->ifTrue(static fn ($v) => \is_array($v) && 1 < \count($v))
64+
->then(static fn () => throw new InvalidConfigurationException('You cannot configure multiple token handlers.'))
7665
->end()
7766

7867
// "isRequired" must be set otherwise the following custom validation is not called
7968
->isRequired()
80-
->beforeNormalization()
81-
->ifTrue(fn ($v) => \is_array($v) && !$v)
82-
->then(fn () => throw new InvalidConfigurationException('You must set a token handler.'))
69+
->validate()
70+
->ifTrue(static fn ($v) => \is_array($v) && !$v)
71+
->then(static fn () => throw new InvalidConfigurationException('You must set a token handler.'))
8372
->end()
8473

8574
->children()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ public function addConfiguration(NodeDefinition $node): void
133133
->end()
134134
->scalarNode('service')->end()
135135
->arrayNode('user_providers', 'user_provider')
136-
->beforeNormalization()
137-
->ifString()->then(fn ($v) => [$v])
138-
->end()
136+
->acceptAndWrap(['string'])
139137
->prototype('scalar')->end()
140138
->end()
141139
->booleanNode('catch_exceptions')->defaultTrue()->end()
@@ -147,11 +145,9 @@ public function addConfiguration(NodeDefinition $node): void
147145
->defaultValue(['password'])
148146
->end()
149147
->arrayNode('token_provider')
150-
->beforeNormalization()
151-
->ifString()->then(fn ($v) => ['service' => $v])
152-
->end()
148+
->acceptAndWrap(['string'], 'service')
153149
->children()
154-
->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end()
150+
->scalarNode('service')->info('The service ID of a custom remember-me token provider.')->end()
155151
->arrayNode('doctrine')
156152
->canBeEnabled()
157153
->children()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
1313

14+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1415
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
1516
use Symfony\Component\DependencyInjection\ChildDefinition;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -42,6 +43,9 @@ public function getKey(): string
4243
return 'memory';
4344
}
4445

46+
/**
47+
* @param ArrayNodeDefinition $node
48+
*/
4549
public function addConfiguration(NodeDefinition $node): void
4650
{
4751
$node
@@ -53,7 +57,7 @@ public function addConfiguration(NodeDefinition $node): void
5357
->children()
5458
->scalarNode('password')->defaultNull()->end()
5559
->arrayNode('roles')
56-
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
60+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
5761
->prototype('scalar')->end()
5862
->end()
5963
->end()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider;
1313

14+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1415
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
1516
use Symfony\Component\DependencyInjection\ChildDefinition;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -45,6 +46,9 @@ public function getKey(): string
4546
return 'ldap';
4647
}
4748

49+
/**
50+
* @param ArrayNodeDefinition $node
51+
*/
4852
public function addConfiguration(NodeDefinition $node): void
4953
{
5054
$node
@@ -57,7 +61,7 @@ public function addConfiguration(NodeDefinition $node): void
5761
->prototype('scalar')->end()
5862
->end()
5963
->arrayNode('default_roles', 'default_role')
60-
->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end()
64+
->beforeNormalization()->ifString()->then(static fn ($v) => preg_split('/\s*,\s*/', $v))->end()
6165
->requiresAtLeastOneElement()
6266
->prototype('scalar')->end()
6367
->end()

0 commit comments

Comments
 (0)