From e6d62fb39644728eedae33d72b3022c3be239532 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 29 Apr 2021 02:48:52 +0200 Subject: [PATCH] Handle ignoreExtraKeys in config builder --- .../Component/Config/Builder/ClassBuilder.php | 13 ++++- .../Config/Builder/ConfigBuilderGenerator.php | 56 +++++++++++++++---- .../Component/Config/Definition/ArrayNode.php | 8 +++ .../Builder/Fixtures/AddToList.config.php | 2 +- .../Builder/Fixtures/AddToList.output.php | 14 ++--- .../Tests/Builder/Fixtures/AddToList.php | 1 - .../Fixtures/ArrayExtraKeys.config.php | 31 ++++++++++ .../Fixtures/ArrayExtraKeys.output.php | 27 +++++++++ .../Tests/Builder/Fixtures/ArrayExtraKeys.php | 36 ++++++++++++ .../Fixtures/NodeInitialValues.config.php | 6 +- .../Fixtures/NodeInitialValues.output.php | 14 ++--- .../Builder/Fixtures/NodeInitialValues.php | 1 - .../Builder/Fixtures/Placeholders.config.php | 1 + .../Tests/Builder/GeneratedConfigTest.php | 10 ++++ 14 files changed, 189 insertions(+), 31 deletions(-) create mode 100644 src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.config.php create mode 100644 src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php create mode 100644 src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.php diff --git a/src/Symfony/Component/Config/Builder/ClassBuilder.php b/src/Symfony/Component/Config/Builder/ClassBuilder.php index c5726f14a689..02aaee82f3b9 100644 --- a/src/Symfony/Component/Config/Builder/ClassBuilder.php +++ b/src/Symfony/Component/Config/Builder/ClassBuilder.php @@ -34,6 +34,7 @@ class ClassBuilder private $require = []; private $use = []; private $implements = []; + private $allowExtraKeys = false; public function __construct(string $namespace, string $name) { @@ -127,7 +128,7 @@ public function addMethod(string $name, string $body, array $params = []): void public function addProperty(string $name, string $classType = null): Property { - $property = new Property($name, $this->camelCase($name)); + $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name); if (null !== $classType) { $property->setType($classType); } @@ -163,4 +164,14 @@ public function getFqcn(): string { return '\\'.$this->namespace.'\\'.$this->name; } + + public function setAllowExtraKeys(bool $allowExtraKeys): void + { + $this->allowExtraKeys = $allowExtraKeys; + } + + public function shouldAllowExtraKeys(): bool + { + return $this->allowExtraKeys; + } } diff --git a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php index 6034f9c1cdb3..300148e20536 100644 --- a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php +++ b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php @@ -59,8 +59,7 @@ public function build(ConfigurationInterface $configuration): \Closure public function NAME(): string { return \'ALIAS\'; -} - ', ['ALIAS' => $rootNode->getPath()]); +}', ['ALIAS' => $rootNode->getPath()]); $this->writeClasses(); } @@ -90,6 +89,7 @@ private function writeClasses(): void foreach ($this->classes as $class) { $this->buildConstructor($class); $this->buildToArray($class); + $this->buildSetExtraKey($class); file_put_contents($this->getFullPath($class), $class->build()); } @@ -126,6 +126,7 @@ private function buildNode(NodeInterface $node, ClassBuilder $class, string $nam private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace): void { $childClass = new ClassBuilder($namespace, $node->getName()); + $childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys()); $class->addRequire($childClass); $this->classes[] = $childClass; @@ -163,7 +164,7 @@ public function NAME($valueDEFAULT): self return $this; }'; - $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']); + $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']); } private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void @@ -211,6 +212,9 @@ public function NAME(string $VAR, $VALUE): self } $childClass = new ClassBuilder($namespace, $name); + if ($prototype instanceof ArrayNode) { + $childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys()); + } $class->addRequire($childClass); $this->classes[] = $childClass; $property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]'); @@ -368,14 +372,15 @@ private function buildToArray(ClassBuilder $class): void }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]); } + $extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : ''; + $class->addMethod('toArray', ' public function NAME(): array { '.$body.' - return $output; -} -'); + return $output'.$extraKeys.'; +}'); } private function buildConstructor(ClassBuilder $class): void @@ -399,18 +404,49 @@ private function buildConstructor(ClassBuilder $class): void ', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]); } - $body .= ' + if ($class->shouldAllowExtraKeys()) { + $body .= ' + $this->_extraKeys = $value; +'; + } else { + $body .= ' if ([] !== $value) { throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value))); }'; - $class->addUse(InvalidConfigurationException::class); + $class->addUse(InvalidConfigurationException::class); + } + $class->addMethod('__construct', ' public function __construct(array $value = []) { '.$body.' -} -'); +}'); + } + + private function buildSetExtraKey(ClassBuilder $class): void + { + if (!$class->shouldAllowExtraKeys()) { + return; + } + + $class->addProperty('_extraKeys'); + + $class->addMethod('set', ' +/** + * @param ParamConfigurator|mixed $value + * @return $this + */ +public function NAME(string $key, $value): self +{ + if (null === $value) { + unset($this->_extraKeys[$key]); + } else { + $this->_extraKeys[$key] = $value; + } + + return $this; +}'); } private function getSubNamespace(ClassBuilder $rootClass): string diff --git a/src/Symfony/Component/Config/Definition/ArrayNode.php b/src/Symfony/Component/Config/Definition/ArrayNode.php index fb17f30338ee..78284d36cb76 100644 --- a/src/Symfony/Component/Config/Definition/ArrayNode.php +++ b/src/Symfony/Component/Config/Definition/ArrayNode.php @@ -140,6 +140,14 @@ public function setIgnoreExtraKeys(bool $boolean, bool $remove = true) $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; } + /** + * Returns true when extra keys should be ignored without an exception. + */ + public function shouldIgnoreExtraKeys(): bool + { + return $this->ignoreExtraKeys; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php index b351c25130ed..5bb32b89be99 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php @@ -12,7 +12,7 @@ 'Foo\\MyArrayMessage' => [ 'senders' => ['workqueue'], ], - ] + ], ]); $config->messenger() ->routing('Foo\\Message')->senders(['workqueue']); diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php index 2a605032f8cb..6efdb8383960 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php @@ -6,17 +6,17 @@ 'sources' => [ '\\Acme\\Foo' => 'yellow', '\\Acme\\Bar' => 'green', - ] + ], ], 'messenger' => [ 'routing' => [ - 'Foo\\MyArrayMessage'=> ['senders'=>['workqueue']], - 'Foo\\Message'=> ['senders'=>['workqueue']], - 'Foo\\DoubleMessage' => ['senders'=>['sync', 'workqueue']], + 'Foo\\MyArrayMessage' => ['senders' => ['workqueue']], + 'Foo\\Message' => ['senders' => ['workqueue']], + 'Foo\\DoubleMessage' => ['senders' => ['sync', 'workqueue']], ], 'receiving' => [ - ['priority'=>10, 'color'=>'blue'], - ['priority'=>5, 'color'=>'red'], - ] + ['priority' => 10, 'color' => 'blue'], + ['priority' => 5, 'color' => 'red'], + ], ], ]; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php index 3be63c8f428f..949cbe9e4e5a 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php @@ -4,7 +4,6 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\Translation\Translator; class AddToList implements ConfigurationInterface { diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.config.php new file mode 100644 index 000000000000..45069e7490c2 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.config.php @@ -0,0 +1,31 @@ +foo([ + 'extra1' => 'foo_extra1', + ]) + ->baz('foo_baz') + ->qux('foo_qux') + ->set('extra2', 'foo_extra2') + ->set('extra3', 'foo_extra3'); + + $config->bar([ + 'extra1' => 'bar1_extra1', + ]) + ->corge('bar1_corge') + ->grault('bar1_grault') + ->set('extra2', 'bar1_extra2') + ->set('extra3', 'bar1_extra3'); + + $config->bar([ + 'extra1' => 'bar2_extra1', + 'extra4' => 'bar2_extra4', + ]) + ->corge('bar2_corge') + ->grault('bar2_grault') + ->set('extra2', 'bar2_extra2') + ->set('extra3', 'bar2_extra3') + ->set('extra4', null); +}; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php new file mode 100644 index 000000000000..d1bdedcf8a23 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.output.php @@ -0,0 +1,27 @@ + [ + 'baz' => 'foo_baz', + 'qux' => 'foo_qux', + 'extra1' => 'foo_extra1', + 'extra2' => 'foo_extra2', + 'extra3' => 'foo_extra3', + ], + 'bar' => [ + [ + 'corge' => 'bar1_corge', + 'grault' => 'bar1_grault', + 'extra1' => 'bar1_extra1', + 'extra2' => 'bar1_extra2', + 'extra3' => 'bar1_extra3', + ], + [ + 'corge' => 'bar2_corge', + 'grault' => 'bar2_grault', + 'extra1' => 'bar2_extra1', + 'extra2' => 'bar2_extra2', + 'extra3' => 'bar2_extra3', + ], + ], +]; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.php new file mode 100644 index 000000000000..453468330b26 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/ArrayExtraKeys.php @@ -0,0 +1,36 @@ +getRootNode(); + $rootNode + ->children() + ->arrayNode('foo') + ->ignoreExtraKeys(false) + ->children() + ->scalarNode('baz')->end() + ->scalarNode('qux')->end() + ->end() + ->end() + ->arrayNode('bar') + ->prototype('array') + ->ignoreExtraKeys(false) + ->children() + ->scalarNode('corge')->end() + ->scalarNode('grault')->end() + ->end() + ->end() + ->end() + ; + + return $tb; + } +} diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php index 4ebc4df24c39..c51bd764e00e 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php @@ -3,13 +3,13 @@ use Symfony\Config\NodeInitialValuesConfig; return static function (NodeInitialValuesConfig $config) { - $config->someCleverName(['second'=>'foo'])->first('bar'); + $config->someCleverName(['second' => 'foo'])->first('bar'); $config->messenger() - ->transports('fast_queue', ['dsn'=>'sync://']) + ->transports('fast_queue', ['dsn' => 'sync://']) ->serializer('acme'); $config->messenger() ->transports('slow_queue') ->dsn('doctrine://') - ->options(['table'=>'my_messages']); + ->options(['table' => 'my_messages']); }; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php index f1d839ea9cd7..ec8fee9a6d1d 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php @@ -8,13 +8,13 @@ 'messenger' => [ 'transports' => [ 'fast_queue' => [ - 'dsn'=>'sync://', - 'serializer'=>'acme', + 'dsn' => 'sync://', + 'serializer' => 'acme', ], 'slow_queue' => [ - 'dsn'=>'doctrine://', - 'options'=>['table'=>'my_messages'], - ] - ] - ] + 'dsn' => 'doctrine://', + 'options' => ['table' => 'my_messages'], + ], + ], + ], ]; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php index 35b2d0d928a2..13fdf1ae81d1 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php @@ -4,7 +4,6 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\Translation\Translator; class NodeInitialValues implements ConfigurationInterface { diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php index 3423f9466d86..089252f32f34 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php @@ -1,6 +1,7 @@ 'variable_type', 'AddToList' => 'add_to_list', 'NodeInitialValues' => 'node_initial_values', + 'ArrayExtraKeys' => 'array_extra_keys', ]; foreach ($array as $name => $alias) { @@ -105,6 +106,15 @@ public function testWrongInitialValues() $configBuilder->someCleverName(['not_exists' => 'foo']); } + public function testSetExtraKeyMethodIsNotGeneratedWhenAllowExtraKeysIsFalse() + { + /** @var AddToListConfig $configBuilder */ + $configBuilder = $this->generateConfigBuilder(AddToList::class); + + $this->assertFalse(method_exists($configBuilder->translator(), 'set')); + $this->assertFalse(method_exists($configBuilder->messenger()->receiving(), 'set')); + } + /** * Generate the ConfigBuilder or return an already generated instance. */