diff --git a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php index 333d323aca29c..0ae8b86236059 100644 --- a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php +++ b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php @@ -127,10 +127,13 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n $class->addRequire($childClass); $this->classes[] = $childClass; + $nodeTypes = $this->getParameterTypes($node); + $paramType = $this->getParamType($nodeTypes); + $hasNormalizationClosures = $this->hasNormalizationClosures($node); $comment = $this->getComment($node); - if ($hasNormalizationClosures) { - $comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + if ($hasNormalizationClosures && 'array' !== $paramType) { + $comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment); $comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); $comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); } @@ -142,8 +145,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n $node->getName(), $this->getType($childClass->getFqcn(), $hasNormalizationClosures) ); - $nodeTypes = $this->getParameterTypes($node); - $body = $hasNormalizationClosures ? ' + $body = $hasNormalizationClosures && 'array' !== $paramType ? ' COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static { if (!\is_array($value)) { @@ -178,7 +180,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n 'COMMENT' => $comment, 'PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), - 'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes), + 'PARAM_TYPE' => $paramType, ]); $this->buildNode($node, $childClass, $this->getSubNamespace($childClass)); @@ -218,10 +220,11 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild $nodeParameterTypes = $this->getParameterTypes($node); $prototypeParameterTypes = $this->getParameterTypes($prototype); + $noKey = null === $key = $node->getKeyAttribute(); if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) { $class->addUse(ParamConfigurator::class); $property = $class->addProperty($node->getName()); - if (null === $key = $node->getKeyAttribute()) { + if ($noKey) { // This is an array of values; don't use singular name $nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type); $body = ' @@ -242,7 +245,7 @@ public function NAME(PARAM_TYPE $value): static 'PROPERTY' => $property->getName(), 'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes), 'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '', - 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeParameterTypes), + 'PARAM_TYPE' => $this->getParamType($nodeParameterTypes, true), ]); } else { $body = ' @@ -259,7 +262,7 @@ public function NAME(string $VAR, TYPE $VALUE): static $class->addMethod($methodName, $body, [ 'PROPERTY' => $property->getName(), - 'TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeParameterTypes), + 'TYPE' => $this->getParamType($prototypeParameterTypes, true), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value', ]); @@ -280,18 +283,27 @@ public function NAME(string $VAR, TYPE $VALUE): static $this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures) ); + $paramType = $this->getParamType($noKey ? $nodeParameterTypes : $prototypeParameterTypes); + $comment = $this->getComment($node); +<<<<<<< HEAD if ($hasNormalizationClosures) { $comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); $comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); $comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); +======= + if ($hasNormalizationClosures && 'array' !== $paramType) { + $comment = sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment); + $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); +>>>>>>> 100c683018d ([Config] Do not generate unreachable configuration paths) } if ('' !== $comment) { $comment = "/**\n$comment*/\n"; } - if (null === $key = $node->getKeyAttribute()) { - $body = $hasNormalizationClosures ? ' + if ($noKey) { + $body = $hasNormalizationClosures && 'array' !== $paramType ? ' COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static { $this->_usedProperties[\'PROPERTY\'] = true; @@ -313,10 +325,10 @@ public function NAME(string $VAR, TYPE $VALUE): static 'COMMENT' => $comment, 'PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), - 'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : implode('|', $nodeParameterTypes), + 'PARAM_TYPE' => $paramType, ]); } else { - $body = $hasNormalizationClosures ? ' + $body = $hasNormalizationClosures && 'array' !== $paramType ? ' COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static { if (!\is_array($VALUE)) { @@ -352,7 +364,7 @@ public function NAME(string $VAR, TYPE $VALUE): static 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value', - 'PARAM_TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : implode('|', $prototypeParameterTypes), + 'PARAM_TYPE' => $paramType, ]); } @@ -597,4 +609,9 @@ private function getType(string $classType, bool $hasNormalizationClosures): str { return $classType.($hasNormalizationClosures ? '|scalar' : ''); } + + private function getParamType(array $types, bool $withParamConfigurator = false): string + { + return \in_array('mixed', $types, true) ? 'mixed' : ($withParamConfigurator ? 'ParamConfigurator|' : '').implode('|', $types); + } } diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.config.php new file mode 100644 index 0000000000000..642345ca5dd63 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.config.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Config\ArrayValuesConfig; + +return static function (ArrayValuesConfig $config) { + $config->transports('foo')->dsn('bar'); + $config->transports('bar', ['dsn' => 'foobar']); + + $config->errorPages()->withTrace(false); +}; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.output.php new file mode 100644 index 0000000000000..ab56d53a2f562 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.output.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'transports' => [ + 'foo' => [ + 'dsn' => 'bar', + ], + 'bar' => [ + 'dsn' => 'foobar', + ], + ], + 'error_pages' => [ + 'with_trace' => false, + ] +]; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.php new file mode 100644 index 0000000000000..457e78a1f7118 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues.php @@ -0,0 +1,42 @@ +getRootNode(); + $rootNode + ->children() + ->arrayNode('transports') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->beforeNormalization() + ->ifString() + ->then(function (string $dsn) { + return ['dsn' => $dsn]; + }) + ->end() + ->fixXmlConfig('option') + ->children() + ->scalarNode('dsn')->end() + ->end() + ->end() + ->end() + ->arrayNode('error_pages') + ->canBeEnabled() + ->children() + ->booleanNode('with_trace')->end() + ->end() + ->end() + ->end(); + + return $tb; + } +} diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/ErrorPagesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/ErrorPagesConfig.php new file mode 100644 index 0000000000000..36ccb016dc6d9 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/ErrorPagesConfig.php @@ -0,0 +1,75 @@ +_usedProperties['enabled'] = true; + $this->enabled = $value; + + return $this; + } + + /** + * @default null + * @param ParamConfigurator|bool $value + * @return $this + */ + public function withTrace($value): static + { + $this->_usedProperties['withTrace'] = true; + $this->withTrace = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('enabled', $value)) { + $this->_usedProperties['enabled'] = true; + $this->enabled = $value['enabled']; + unset($value['enabled']); + } + + if (array_key_exists('with_trace', $value)) { + $this->_usedProperties['withTrace'] = true; + $this->withTrace = $value['with_trace']; + unset($value['with_trace']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['enabled'])) { + $output['enabled'] = $this->enabled; + } + if (isset($this->_usedProperties['withTrace'])) { + $output['with_trace'] = $this->withTrace; + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/TransportsConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/TransportsConfig.php new file mode 100644 index 0000000000000..c1ad5437834f8 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValues/TransportsConfig.php @@ -0,0 +1,52 @@ +_usedProperties['dsn'] = true; + $this->dsn = $value; + + return $this; + } + + public function __construct(array $value = []) + { + if (array_key_exists('dsn', $value)) { + $this->_usedProperties['dsn'] = true; + $this->dsn = $value['dsn']; + unset($value['dsn']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['dsn'])) { + $output['dsn'] = $this->dsn; + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValuesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValuesConfig.php new file mode 100644 index 0000000000000..818a14fcbcd9b --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayValues/Symfony/Config/ArrayValuesConfig.php @@ -0,0 +1,96 @@ +_usedProperties['transports'] = true; + $this->transports[$name] = $value; + + return $this; + } + + if (!isset($this->transports[$name]) || !$this->transports[$name] instanceof \Symfony\Config\ArrayValues\TransportsConfig) { + $this->_usedProperties['transports'] = true; + $this->transports[$name] = new \Symfony\Config\ArrayValues\TransportsConfig($value); + } elseif (1 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "transports()" has already been initialized. You cannot pass values the second time you call transports().'); + } + + return $this->transports[$name]; + } + + /** + * @default {"enabled":false} + */ + public function errorPages(array $value = []): \Symfony\Config\ArrayValues\ErrorPagesConfig + { + if (null === $this->errorPages) { + $this->_usedProperties['errorPages'] = true; + $this->errorPages = new \Symfony\Config\ArrayValues\ErrorPagesConfig($value); + } elseif (0 < \func_num_args()) { + throw new InvalidConfigurationException('The node created by "errorPages()" has already been initialized. You cannot pass values the second time you call errorPages().'); + } + + return $this->errorPages; + } + + public function getExtensionAlias(): string + { + return 'array_values'; + } + + public function __construct(array $value = []) + { + if (array_key_exists('transports', $value)) { + $this->_usedProperties['transports'] = true; + $this->transports = array_map(fn ($v) => \is_array($v) ? new \Symfony\Config\ArrayValues\TransportsConfig($v) : $v, $value['transports']); + unset($value['transports']); + } + + if (array_key_exists('error_pages', $value)) { + $this->_usedProperties['errorPages'] = true; + $this->errorPages = \is_array($value['error_pages']) ? new \Symfony\Config\ArrayValues\ErrorPagesConfig($value['error_pages']) : $value['error_pages']; + unset($value['error_pages']); + } + + if ([] !== $value) { + throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value))); + } + } + + public function toArray(): array + { + $output = []; + if (isset($this->_usedProperties['transports'])) { + $output['transports'] = array_map(fn ($v) => $v instanceof \Symfony\Config\ArrayValues\TransportsConfig ? $v->toArray() : $v, $this->transports); + } + if (isset($this->_usedProperties['errorPages'])) { + $output['error_pages'] = $this->errorPages instanceof \Symfony\Config\ArrayValues\ErrorPagesConfig ? $this->errorPages->toArray() : $this->errorPages; + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypes/NestedConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypes/NestedConfig.php index 2cc1fb3275e78..2b3f19e87fb6f 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypes/NestedConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypes/NestedConfig.php @@ -17,7 +17,7 @@ class NestedConfig private $_usedProperties = []; /** - * @template TValue + * @template TValue of mixed * @param TValue $value * @default {"enabled":null} * @return \Symfony\Config\ScalarNormalizedTypes\Nested\NestedObjectConfig|$this @@ -43,7 +43,7 @@ public function nestedObject(mixed $value = []): \Symfony\Config\ScalarNormalize } /** - * @template TValue + * @template TValue of mixed * @param TValue $value * @return \Symfony\Config\ScalarNormalizedTypes\Nested\NestedListObjectConfig|$this * @psalm-return (TValue is array ? \Symfony\Config\ScalarNormalizedTypes\Nested\NestedListObjectConfig : static) diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php index 1794ede72e18c..66107b8f19730 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php @@ -48,7 +48,7 @@ public function keyedArray(string $name, ParamConfigurator|string|array $value): } /** - * @template TValue + * @template TValue of mixed * @param TValue $value * @default {"enabled":null} * @return \Symfony\Config\ScalarNormalizedTypes\ObjectConfig|$this @@ -74,7 +74,7 @@ public function object(mixed $value = []): \Symfony\Config\ScalarNormalizedTypes } /** - * @template TValue + * @template TValue of mixed * @param TValue $value * @return \Symfony\Config\ScalarNormalizedTypes\ListObjectConfig|$this * @psalm-return (TValue is array ? \Symfony\Config\ScalarNormalizedTypes\ListObjectConfig : static) @@ -92,7 +92,7 @@ public function listObject(mixed $value = []): \Symfony\Config\ScalarNormalizedT } /** - * @template TValue + * @template TValue of mixed * @param TValue $value * @return \Symfony\Config\ScalarNormalizedTypes\KeyedListObjectConfig|$this * @psalm-return (TValue is array ? \Symfony\Config\ScalarNormalizedTypes\KeyedListObjectConfig : static) diff --git a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php index c15e4776897ea..d722807043aea 100644 --- a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php +++ b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php @@ -62,6 +62,7 @@ public static function fixtureNames() 'AddToList' => 'add_to_list', 'NodeInitialValues' => 'node_initial_values', 'ArrayExtraKeys' => 'array_extra_keys', + 'ArrayValues' => 'array_values', ]; foreach ($array as $name => $alias) {
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: