Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b975e4c

Browse filesBrowse files
committed
feature #40987 [Config] Handle ignoreExtraKeys in config builder (HypeMC)
This PR was merged into the 5.4 branch. Discussion ---------- [Config] Handle ignoreExtraKeys in config builder | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Currently the config builder doesn't handle the `ignoreExtraKeys` option. This is an attempt to fix that. #### TODO - [x] Tests, but I was hoping for some initial feedback first Commits ------- e6d62fb Handle ignoreExtraKeys in config builder
2 parents 2073597 + e6d62fb commit b975e4c
Copy full SHA for b975e4c
Expand file treeCollapse file tree

14 files changed

+189
-31
lines changed

‎src/Symfony/Component/Config/Builder/ClassBuilder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Builder/ClassBuilder.php
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ClassBuilder
3434
private $require = [];
3535
private $use = [];
3636
private $implements = [];
37+
private $allowExtraKeys = false;
3738

3839
public function __construct(string $namespace, string $name)
3940
{
@@ -127,7 +128,7 @@ public function addMethod(string $name, string $body, array $params = []): void
127128

128129
public function addProperty(string $name, string $classType = null): Property
129130
{
130-
$property = new Property($name, $this->camelCase($name));
131+
$property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name);
131132
if (null !== $classType) {
132133
$property->setType($classType);
133134
}
@@ -163,4 +164,14 @@ public function getFqcn(): string
163164
{
164165
return '\\'.$this->namespace.'\\'.$this->name;
165166
}
167+
168+
public function setAllowExtraKeys(bool $allowExtraKeys): void
169+
{
170+
$this->allowExtraKeys = $allowExtraKeys;
171+
}
172+
173+
public function shouldAllowExtraKeys(): bool
174+
{
175+
return $this->allowExtraKeys;
176+
}
166177
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php
+46-10Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ public function build(ConfigurationInterface $configuration): \Closure
5959
public function NAME(): string
6060
{
6161
return \'ALIAS\';
62-
}
63-
', ['ALIAS' => $rootNode->getPath()]);
62+
}', ['ALIAS' => $rootNode->getPath()]);
6463

6564
$this->writeClasses();
6665
}
@@ -90,6 +89,7 @@ private function writeClasses(): void
9089
foreach ($this->classes as $class) {
9190
$this->buildConstructor($class);
9291
$this->buildToArray($class);
92+
$this->buildSetExtraKey($class);
9393

9494
file_put_contents($this->getFullPath($class), $class->build());
9595
}
@@ -126,6 +126,7 @@ private function buildNode(NodeInterface $node, ClassBuilder $class, string $nam
126126
private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace): void
127127
{
128128
$childClass = new ClassBuilder($namespace, $node->getName());
129+
$childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys());
129130
$class->addRequire($childClass);
130131
$this->classes[] = $childClass;
131132

@@ -163,7 +164,7 @@ public function NAME($valueDEFAULT): self
163164
164165
return $this;
165166
}';
166-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']);
167+
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']);
167168
}
168169

169170
private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void
@@ -211,6 +212,9 @@ public function NAME(string $VAR, $VALUE): self
211212
}
212213

213214
$childClass = new ClassBuilder($namespace, $name);
215+
if ($prototype instanceof ArrayNode) {
216+
$childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys());
217+
}
214218
$class->addRequire($childClass);
215219
$this->classes[] = $childClass;
216220
$property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]');
@@ -368,14 +372,15 @@ private function buildToArray(ClassBuilder $class): void
368372
}', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
369373
}
370374

375+
$extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
376+
371377
$class->addMethod('toArray', '
372378
public function NAME(): array
373379
{
374380
'.$body.'
375381
376-
return $output;
377-
}
378-
');
382+
return $output'.$extraKeys.';
383+
}');
379384
}
380385

381386
private function buildConstructor(ClassBuilder $class): void
@@ -399,18 +404,49 @@ private function buildConstructor(ClassBuilder $class): void
399404
', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
400405
}
401406

402-
$body .= '
407+
if ($class->shouldAllowExtraKeys()) {
408+
$body .= '
409+
$this->_extraKeys = $value;
410+
';
411+
} else {
412+
$body .= '
403413
if ([] !== $value) {
404414
throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value)));
405415
}';
406416

407-
$class->addUse(InvalidConfigurationException::class);
417+
$class->addUse(InvalidConfigurationException::class);
418+
}
419+
408420
$class->addMethod('__construct', '
409421
public function __construct(array $value = [])
410422
{
411423
'.$body.'
412-
}
413-
');
424+
}');
425+
}
426+
427+
private function buildSetExtraKey(ClassBuilder $class): void
428+
{
429+
if (!$class->shouldAllowExtraKeys()) {
430+
return;
431+
}
432+
433+
$class->addProperty('_extraKeys');
434+
435+
$class->addMethod('set', '
436+
/**
437+
* @param ParamConfigurator|mixed $value
438+
* @return $this
439+
*/
440+
public function NAME(string $key, $value): self
441+
{
442+
if (null === $value) {
443+
unset($this->_extraKeys[$key]);
444+
} else {
445+
$this->_extraKeys[$key] = $value;
446+
}
447+
448+
return $this;
449+
}');
414450
}
415451

416452
private function getSubNamespace(ClassBuilder $rootClass): string

‎src/Symfony/Component/Config/Definition/ArrayNode.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Definition/ArrayNode.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ public function setIgnoreExtraKeys(bool $boolean, bool $remove = true)
140140
$this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
141141
}
142142

143+
/**
144+
* Returns true when extra keys should be ignored without an exception.
145+
*/
146+
public function shouldIgnoreExtraKeys(): bool
147+
{
148+
return $this->ignoreExtraKeys;
149+
}
150+
143151
/**
144152
* {@inheritdoc}
145153
*/

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'Foo\\MyArrayMessage' => [
1313
'senders' => ['workqueue'],
1414
],
15-
]
15+
],
1616
]);
1717
$config->messenger()
1818
->routing('Foo\\Message')->senders(['workqueue']);

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.output.php
+7-7Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
'sources' => [
77
'\\Acme\\Foo' => 'yellow',
88
'\\Acme\\Bar' => 'green',
9-
]
9+
],
1010
],
1111
'messenger' => [
1212
'routing' => [
13-
'Foo\\MyArrayMessage'=> ['senders'=>['workqueue']],
14-
'Foo\\Message'=> ['senders'=>['workqueue']],
15-
'Foo\\DoubleMessage' => ['senders'=>['sync', 'workqueue']],
13+
'Foo\\MyArrayMessage' => ['senders' => ['workqueue']],
14+
'Foo\\Message' => ['senders' => ['workqueue']],
15+
'Foo\\DoubleMessage' => ['senders' => ['sync', 'workqueue']],
1616
],
1717
'receiving' => [
18-
['priority'=>10, 'color'=>'blue'],
19-
['priority'=>5, 'color'=>'red'],
20-
]
18+
['priority' => 10, 'color' => 'blue'],
19+
['priority' => 5, 'color' => 'red'],
20+
],
2121
],
2222
];

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.php
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
66
use Symfony\Component\Config\Definition\ConfigurationInterface;
7-
use Symfony\Component\Translation\Translator;
87

98
class AddToList implements ConfigurationInterface
109
{
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use Symfony\Config\ArrayExtraKeysConfig;
4+
5+
return static function (ArrayExtraKeysConfig $config) {
6+
$config->foo([
7+
'extra1' => 'foo_extra1',
8+
])
9+
->baz('foo_baz')
10+
->qux('foo_qux')
11+
->set('extra2', 'foo_extra2')
12+
->set('extra3', 'foo_extra3');
13+
14+
$config->bar([
15+
'extra1' => 'bar1_extra1',
16+
])
17+
->corge('bar1_corge')
18+
->grault('bar1_grault')
19+
->set('extra2', 'bar1_extra2')
20+
->set('extra3', 'bar1_extra3');
21+
22+
$config->bar([
23+
'extra1' => 'bar2_extra1',
24+
'extra4' => 'bar2_extra4',
25+
])
26+
->corge('bar2_corge')
27+
->grault('bar2_grault')
28+
->set('extra2', 'bar2_extra2')
29+
->set('extra3', 'bar2_extra3')
30+
->set('extra4', null);
31+
};
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
return [
4+
'foo' => [
5+
'baz' => 'foo_baz',
6+
'qux' => 'foo_qux',
7+
'extra1' => 'foo_extra1',
8+
'extra2' => 'foo_extra2',
9+
'extra3' => 'foo_extra3',
10+
],
11+
'bar' => [
12+
[
13+
'corge' => 'bar1_corge',
14+
'grault' => 'bar1_grault',
15+
'extra1' => 'bar1_extra1',
16+
'extra2' => 'bar1_extra2',
17+
'extra3' => 'bar1_extra3',
18+
],
19+
[
20+
'corge' => 'bar2_corge',
21+
'grault' => 'bar2_grault',
22+
'extra1' => 'bar2_extra1',
23+
'extra2' => 'bar2_extra2',
24+
'extra3' => 'bar2_extra3',
25+
],
26+
],
27+
];
+36Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Symfony\Component\Config\Tests\Builder\Fixtures;
4+
5+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
6+
use Symfony\Component\Config\Definition\ConfigurationInterface;
7+
8+
class ArrayExtraKeys implements ConfigurationInterface
9+
{
10+
public function getConfigTreeBuilder(): TreeBuilder
11+
{
12+
$tb = new TreeBuilder('array_extra_keys');
13+
$rootNode = $tb->getRootNode();
14+
$rootNode
15+
->children()
16+
->arrayNode('foo')
17+
->ignoreExtraKeys(false)
18+
->children()
19+
->scalarNode('baz')->end()
20+
->scalarNode('qux')->end()
21+
->end()
22+
->end()
23+
->arrayNode('bar')
24+
->prototype('array')
25+
->ignoreExtraKeys(false)
26+
->children()
27+
->scalarNode('corge')->end()
28+
->scalarNode('grault')->end()
29+
->end()
30+
->end()
31+
->end()
32+
;
33+
34+
return $tb;
35+
}
36+
}

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.config.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
use Symfony\Config\NodeInitialValuesConfig;
44

55
return static function (NodeInitialValuesConfig $config) {
6-
$config->someCleverName(['second'=>'foo'])->first('bar');
6+
$config->someCleverName(['second' => 'foo'])->first('bar');
77
$config->messenger()
8-
->transports('fast_queue', ['dsn'=>'sync://'])
8+
->transports('fast_queue', ['dsn' => 'sync://'])
99
->serializer('acme');
1010

1111
$config->messenger()
1212
->transports('slow_queue')
1313
->dsn('doctrine://')
14-
->options(['table'=>'my_messages']);
14+
->options(['table' => 'my_messages']);
1515
};

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.output.php
+7-7Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
'messenger' => [
99
'transports' => [
1010
'fast_queue' => [
11-
'dsn'=>'sync://',
12-
'serializer'=>'acme',
11+
'dsn' => 'sync://',
12+
'serializer' => 'acme',
1313
],
1414
'slow_queue' => [
15-
'dsn'=>'doctrine://',
16-
'options'=>['table'=>'my_messages'],
17-
]
18-
]
19-
]
15+
'dsn' => 'doctrine://',
16+
'options' => ['table' => 'my_messages'],
17+
],
18+
],
19+
],
2020
];

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/NodeInitialValues.php
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
66
use Symfony\Component\Config\Definition\ConfigurationInterface;
7-
use Symfony\Component\Translation\Translator;
87

98
class NodeInitialValues implements ConfigurationInterface
109
{

‎src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/Fixtures/Placeholders.config.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
4+
45
use Symfony\Config\PlaceholdersConfig;
56

67
return static function (PlaceholdersConfig $config) {

‎src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function fixtureNames()
2727
'VariableType' => 'variable_type',
2828
'AddToList' => 'add_to_list',
2929
'NodeInitialValues' => 'node_initial_values',
30+
'ArrayExtraKeys' => 'array_extra_keys',
3031
];
3132

3233
foreach ($array as $name => $alias) {
@@ -105,6 +106,15 @@ public function testWrongInitialValues()
105106
$configBuilder->someCleverName(['not_exists' => 'foo']);
106107
}
107108

109+
public function testSetExtraKeyMethodIsNotGeneratedWhenAllowExtraKeysIsFalse()
110+
{
111+
/** @var AddToListConfig $configBuilder */
112+
$configBuilder = $this->generateConfigBuilder(AddToList::class);
113+
114+
$this->assertFalse(method_exists($configBuilder->translator(), 'set'));
115+
$this->assertFalse(method_exists($configBuilder->messenger()->receiving(), 'set'));
116+
}
117+
108118
/**
109119
* Generate the ConfigBuilder or return an already generated instance.
110120
*/

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.