Skip to content

Commit 633169f

Browse files
[Config] Config builders should accept booleans for array nodes that can be enabled or disabled
1 parent c6e3f9b commit 633169f

File tree

4 files changed

+66
-66
lines changed

4 files changed

+66
-66
lines changed

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public function getConfigTreeBuilder(): TreeBuilder
145145
return ContainerBuilder::willBeAvailable($package, $class, $parentPackages);
146146
};
147147

148-
$enableIfStandalone = fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled';
148+
$enableIfStandalone = static fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled';
149149

150150
$this->addCsrfSection($rootNode);
151151
$this->addFormSection($rootNode, $enableIfStandalone);
@@ -1035,6 +1035,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
10351035
->normalizeKeys(false)
10361036
->useAttributeAsKey('name')
10371037
->arrayPrototype()
1038+
->acceptAndWrap(['string'], 'value')
10381039
->children()
10391040
->variableNode('value')->end()
10401041
->stringNode('message')->end()
@@ -1045,7 +1046,6 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
10451046
->end()
10461047
->stringNode('domain')->end()
10471048
->end()
1048-
->acceptAndWrap(['string'], 'value')
10491049
->validate()
10501050
->ifTrue(static fn ($v) => !(isset($v['value']) xor isset($v['message'])))
10511051
->thenInvalid('The "globals" parameter should be either a string or an array with a "value" or a "message" key')
@@ -1154,7 +1154,7 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode): void
11541154
->arrayNode('annotations')
11551155
->canBeEnabled()
11561156
->validate()
1157-
->ifTrue(static fn (array $v) => $v['enabled'])
1157+
->ifTrue(static fn ($v) => $v['enabled'])
11581158
->thenInvalid('Enabling the doctrine/annotations integration is not supported anymore.')
11591159
->end()
11601160
->end()
@@ -1482,24 +1482,25 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI
14821482
->children()
14831483
->arrayNode('lock')
14841484
->info('Lock configuration')
1485-
->{$enableIfStandalone('symfony/lock', Lock::class)}()
14861485
->acceptAndWrap(['string'], 'resources')
1486+
->{$enableIfStandalone('symfony/lock', Lock::class)}()
14871487
->beforeNormalization()
14881488
->ifArray()
14891489
->then(static function ($v) {
1490-
$v += ['enabled' => true];
1491-
14921490
if (!isset($v['resources']) && !isset($v['resource'])) {
1493-
$v = ['enabled' => $v['enabled'], 'resources' => $v];
1494-
unset($v['resources']['enabled']);
1491+
$v = ['resources' => $v];
1492+
if (\array_key_exists('enabled', $v['resources'])) {
1493+
$v['enabled'] = $v['resources']['enabled'];
1494+
unset($v['resources']['enabled']);
1495+
}
14951496
}
14961497

14971498
return $v;
14981499
})
14991500
->end()
15001501
->addDefaultsIfNotSet()
15011502
->validate()
1502-
->ifTrue(fn ($config) => $config['enabled'] && !$config['resources'])
1503+
->ifTrue(static fn ($v) => $v['enabled'] && !$v['resources'])
15031504
->thenInvalid('At least one resource must be defined.')
15041505
->end()
15051506
->children()
@@ -1547,16 +1548,17 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en
15471548
->children()
15481549
->arrayNode('semaphore')
15491550
->info('Semaphore configuration')
1550-
->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}()
15511551
->acceptAndWrap(['string'], 'resources')
1552+
->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}()
15521553
->beforeNormalization()
15531554
->ifArray()
15541555
->then(static function ($v) {
1555-
$v += ['enabled' => true];
1556-
15571556
if (!isset($v['resources']) && !isset($v['resource'])) {
1558-
$v = ['enabled' => $v['enabled'], 'resources' => $v];
1559-
unset($v['resources']['enabled']);
1557+
$v = ['resources' => $v];
1558+
if (\array_key_exists('enabled', $v['resources'])) {
1559+
$v['enabled'] = $v['resources']['enabled'];
1560+
unset($v['resources']['enabled']);
1561+
}
15601562
}
15611563

15621564
return $v;
@@ -2245,7 +2247,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
22452247
->acceptAndWrap(['string'])
22462248
->beforeNormalization()
22472249
->ifArray()
2248-
->then(static fn ($v) => array_filter(array_values($v)))
2250+
->then(static fn ($v) => array_values(array_filter($v)))
22492251
->end()
22502252
->prototype('scalar')->end()
22512253
->end()
@@ -2256,7 +2258,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
22562258
->acceptAndWrap(['string'])
22572259
->beforeNormalization()
22582260
->ifArray()
2259-
->then(static fn ($v) => array_filter(array_values($v)))
2261+
->then(static fn ($v) => array_values(array_filter($v)))
22602262
->end()
22612263
->prototype('scalar')->end()
22622264
->end()
@@ -2453,8 +2455,11 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $
24532455
->ifArray()
24542456
->then(static function ($v) {
24552457
if (!isset($v['limiters']) && !isset($v['limiter'])) {
2456-
$v = ['enabled' => $v['enabled'] ?? true, 'limiters' => $v];
2457-
unset($v['limiters']['enabled']);
2458+
$v = ['limiters' => $v];
2459+
if (\array_key_exists('enabled', $v['limiters'])) {
2460+
$v['enabled'] = $v['limiters']['enabled'];
2461+
unset($v['limiters']['enabled']);
2462+
}
24582463
}
24592464

24602465
return $v;

src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
127127
$this->classes[] = $childClass;
128128

129129
$nodeTypes = $this->getParameterTypes($node);
130-
$paramType = $this->getParamType($nodeTypes);
130+
$paramType = implode('|', $nodeTypes);
131+
$acceptScalar = 'array' !== $paramType;
131132

132-
$hasNormalizationClosures = $this->hasNormalizationClosures($node);
133133
$comment = $this->getComment($node);
134-
if ($hasNormalizationClosures && 'array' !== $paramType) {
134+
if ($acceptScalar) {
135135
$comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
136136
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
137137
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
@@ -142,9 +142,9 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
142142

143143
$property = $class->addProperty(
144144
$node->getName(),
145-
$this->getType($childClass->getFqcn(), $hasNormalizationClosures)
145+
$childClass->getFqcn().($acceptScalar ? '|scalar' : '')
146146
);
147-
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
147+
$body = $acceptScalar ? '
148148
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
149149
{
150150
if (!\is_array($value)) {
@@ -215,17 +215,18 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
215215
$name = $this->getSingularName($node);
216216
$prototype = $node->getPrototype();
217217
$methodName = $name;
218-
$hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype);
219218

220219
$nodeParameterTypes = $this->getParameterTypes($node);
221220
$prototypeParameterTypes = $this->getParameterTypes($prototype);
222221
$noKey = null === $key = $node->getKeyAttribute();
222+
$acceptScalar = ['array'] !== $nodeParameterTypes || ['array'] !== $prototypeParameterTypes;
223+
223224
if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) {
224225
$class->addUse(ParamConfigurator::class);
225226
$property = $class->addProperty($node->getName());
226227
if ($noKey) {
227228
// This is an array of values; don't use singular name
228-
$nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type);
229+
$nodeTypesWithoutArray = array_diff($nodeParameterTypes, ['array']);
229230
$body = '
230231
/**
231232
* @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
@@ -244,7 +245,7 @@ public function NAME(PARAM_TYPE $value): static
244245
'PROPERTY' => $property->getName(),
245246
'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes),
246247
'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '',
247-
'PARAM_TYPE' => $this->getParamType($nodeParameterTypes, true),
248+
'PARAM_TYPE' => ['mixed'] !== $nodeParameterTypes ? 'ParamConfigurator|'.implode('|', $nodeParameterTypes) : 'mixed',
248249
]);
249250
} else {
250251
$body = '
@@ -261,7 +262,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
261262

262263
$class->addMethod($methodName, $body, [
263264
'PROPERTY' => $property->getName(),
264-
'TYPE' => $this->getParamType($prototypeParameterTypes, true),
265+
'TYPE' => ['mixed'] !== $prototypeParameterTypes ? 'ParamConfigurator|'.implode('|', $prototypeParameterTypes) : 'mixed',
265266
'VAR' => '' === $key ? 'key' : $key,
266267
'VALUE' => 'value' === $key ? 'data' : 'value',
267268
]);
@@ -279,13 +280,14 @@ public function NAME(string $VAR, TYPE $VALUE): static
279280

280281
$property = $class->addProperty(
281282
$node->getName(),
282-
$this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures)
283+
$childClass->getFqcn().'[]'.($acceptScalar ? '|scalar' : '')
283284
);
284285

285-
$paramType = $this->getParamType($noKey ? $nodeParameterTypes : $prototypeParameterTypes);
286+
$paramType = implode('|', $noKey ? $nodeParameterTypes : $prototypeParameterTypes);
287+
$acceptScalar = 'array' !== $paramType;
286288

287289
$comment = $this->getComment($node);
288-
if ($hasNormalizationClosures && 'array' !== $paramType) {
290+
if ($acceptScalar) {
289291
$comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
290292
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
291293
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
@@ -295,7 +297,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
295297
}
296298

297299
if ($noKey) {
298-
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
300+
$body = $acceptScalar ? '
299301
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
300302
{
301303
$this->_usedProperties[\'PROPERTY\'] = true;
@@ -320,7 +322,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
320322
'PARAM_TYPE' => $paramType,
321323
]);
322324
} else {
323-
$body = $hasNormalizationClosures && 'array' !== $paramType ? '
325+
$body = $acceptScalar ? '
324326
COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static
325327
{
326328
if (!\is_array($VALUE)) {
@@ -389,8 +391,11 @@ private function getParameterTypes(NodeInterface $node): array
389391
$paramTypes = [];
390392
if ($node instanceof BaseNode) {
391393
foreach ($node->getNormalizedTypes() as $type) {
394+
if (ExprBuilder::TYPE_ANY === $type) {
395+
return ['mixed'];
396+
}
397+
392398
$paramTypes[] = match ($type) {
393-
ExprBuilder::TYPE_ANY => 'mixed',
394399
ExprBuilder::TYPE_STRING => 'string',
395400
ExprBuilder::TYPE_NULL => 'null',
396401
ExprBuilder::TYPE_ARRAY => 'array',
@@ -407,15 +412,9 @@ private function getParameterTypes(NodeInterface $node): array
407412
$paramTypes[] = 'int';
408413
} elseif ($node instanceof FloatNode) {
409414
$paramTypes[] = 'float';
410-
} elseif ($node instanceof EnumNode) {
411-
$paramTypes[] = 'mixed';
412415
} elseif ($node instanceof ArrayNode) {
413416
$paramTypes[] = 'array';
414-
} elseif ($node instanceof VariableNode) {
415-
$paramTypes[] = 'mixed';
416-
}
417-
418-
if (\in_array('mixed', $paramTypes, true)) {
417+
} else {
419418
return ['mixed'];
420419
}
421420

@@ -594,25 +593,4 @@ private function getSubNamespace(ClassBuilder $rootClass): string
594593
{
595594
return \sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6));
596595
}
597-
598-
private function hasNormalizationClosures(NodeInterface $node): bool
599-
{
600-
try {
601-
$r = new \ReflectionProperty($node, 'normalizationClosures');
602-
} catch (\ReflectionException) {
603-
return false;
604-
}
605-
606-
return [] !== $r->getValue($node);
607-
}
608-
609-
private function getType(string $classType, bool $hasNormalizationClosures): string
610-
{
611-
return $classType.($hasNormalizationClosures ? '|scalar' : '');
612-
}
613-
614-
private function getParamType(array $types, bool $withParamConfigurator = false): string
615-
{
616-
return \in_array('mixed', $types, true) ? 'mixed' : ($withParamConfigurator ? 'ParamConfigurator|' : '').implode('|', $types);
617-
}
618596
}

src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ public function canBeEnabled(/* ?string $info = null */): static
354354
->treatNullLike(['enabled' => true])
355355
->beforeNormalization()
356356
->ifArray()
357-
->then(function (array $v) {
357+
->then(static function ($v) {
358358
$v['enabled'] ??= true;
359359

360360
return $v;
@@ -527,8 +527,14 @@ protected function createNode(): NodeInterface
527527
}
528528

529529
if (isset($this->normalization)) {
530+
$allowedTypes = $this->allowedTypes ?? $this->normalization->declaredTypes;
531+
foreach ([$this->trueEquivalent, $this->falseEquivalent] as $equivalent) {
532+
if (\is_array($equivalent) && $equivalent) {
533+
$allowedTypes[] = ExprBuilder::TYPE_BOOL;
534+
}
535+
}
530536
$node->setNormalizationClosures($this->normalization->before);
531-
$node->setNormalizedTypes($this->allowedTypes ?? $this->normalization->declaredTypes);
537+
$node->setNormalizedTypes($allowedTypes ?: [ExprBuilder::TYPE_ARRAY]);
532538
$node->setXmlRemappings($this->normalization->remappings);
533539
}
534540

src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValuesConfig.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,22 @@ public function transports(string $name, string|array $value = []): \Symfony\Con
4242
}
4343

4444
/**
45+
* @template TValue of array|bool
46+
* @param TValue $value
4547
* @default {"enabled":false}
46-
*/
47-
public function errorPages(array $value = []): \Symfony\Config\ArrayValues\ErrorPagesConfig
48+
* @return \Symfony\Config\ArrayValues\ErrorPagesConfig|$this
49+
* @psalm-return (TValue is array ? \Symfony\Config\ArrayValues\ErrorPagesConfig : static)
50+
*/
51+
public function errorPages(array|bool $value = []): \Symfony\Config\ArrayValues\ErrorPagesConfig|static
4852
{
49-
if (null === $this->errorPages) {
53+
if (!\is_array($value)) {
54+
$this->_usedProperties['errorPages'] = true;
55+
$this->errorPages = $value;
56+
57+
return $this;
58+
}
59+
60+
if (!$this->errorPages instanceof \Symfony\Config\ArrayValues\ErrorPagesConfig) {
5061
$this->_usedProperties['errorPages'] = true;
5162
$this->errorPages = new \Symfony\Config\ArrayValues\ErrorPagesConfig($value);
5263
} elseif (0 < \func_num_args()) {

0 commit comments

Comments
 (0)