Skip to content

Commit f5b1cb1

Browse files
committed
[DependencyInjection] Initial implementation of an allowUnnamedChildren method on NodeBuilder. Also added an "extra field" exception.
This allows for an array node, which has any number of child values not represented by nodes.
1 parent 9b60262 commit f5b1cb1

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
3434
protected $minNumberOfElements;
3535
protected $performDeepMerging;
3636
protected $defaultValue;
37+
protected $allowUnnamedChildren;
3738

3839
/**
3940
* Constructor.
@@ -52,6 +53,7 @@ public function __construct($name, NodeInterface $parent = null)
5253
$this->allowNewKeys = true;
5354
$this->performDeepMerging = true;
5455
$this->minNumberOfElements = 0;
56+
$this->allowUnnamedChildren = false;
5557
}
5658

5759
/**
@@ -308,6 +310,14 @@ protected function finalizeValue($value)
308310
}
309311
}
310312

313+
// if extra fields are present and allowUnnamedChildren is false, throw exception
314+
if (!$this->allowUnnamedChildren && $diff = array_diff(array_keys($value), array_keys($this->children))) {
315+
$msg = sprintf('Unrecognized options "%s" under "%s"', implode(', ', $diff), $this->getPath());
316+
317+
throw new InvalidConfigurationException($msg);
318+
}
319+
320+
311321
return $value;
312322
}
313323

@@ -384,16 +394,17 @@ protected function normalizeValue($value)
384394
return $normalized;
385395
}
386396

387-
$normalized = array();
397+
// note that this purposefully does not exclude unrecognized child keys.
398+
// unrecognized keys are just added in - validation takes place in finalize
388399
foreach ($this->children as $name => $child) {
389400
if (!array_key_exists($name, $value)) {
390401
continue;
391402
}
392403

393-
$normalized[$name] = $child->normalize($value[$name]);
404+
$value[$name] = $child->normalize($value[$name]);
394405
}
395406

396-
return $normalized;
407+
return $value;
397408
}
398409

399410
/**
@@ -452,4 +463,19 @@ protected function mergeValues($leftSide, $rightSide)
452463

453464
return $leftSide;
454465
}
466+
467+
/**
468+
* Set whether or not to allow this array to have child values that
469+
* are not represented as nodes.
470+
*
471+
* An example would be an "options" array node, where its children
472+
* could be any key of any form. In this case, no children are placed
473+
* on the node, but child values must be allowed.
474+
*
475+
* @param Boolean $v Whether to allow unnamed children
476+
*/
477+
public function setAllowUnnamedChildren($v)
478+
{
479+
$this->allowUnnamedChildren = $v;
480+
}
455481
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class NodeBuilder
4242
public $trueEquivalent;
4343
public $falseEquivalent;
4444
public $performDeepMerging;
45+
public $allowUnnamedChildren;
4546

4647
/**
4748
* Constructor
@@ -64,6 +65,7 @@ public function __construct($name, $type, $parent = null)
6465
$this->allowEmptyValue = true;
6566
$this->children = array();
6667
$this->performDeepMerging = true;
68+
$this->allowUnnamedChildren = false;
6769

6870
if ('boolean' === $type) {
6971
$this->nullEquivalent = true;
@@ -440,4 +442,20 @@ public function end()
440442
{
441443
return $this->parent;
442444
}
445+
446+
/**
447+
* Allows child values not represented by a node.
448+
*
449+
* An example would be an "options" array node, where its children
450+
* could be any key of any form. In this case, no children are placed
451+
* on the node, but child values must be allowed.
452+
*
453+
* @return Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder
454+
*/
455+
public function allowUnnamedChildren()
456+
{
457+
$this->allowUnnamedChildren = true;
458+
459+
return $this;
460+
}
443461
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ protected function createArrayConfigNode(NodeBuilder $node)
163163
$configNode->addEquivalentValue(false, $node->falseEquivalent);
164164
$configNode->setPerformDeepMerging($node->performDeepMerging);
165165
$configNode->setRequired($node->required);
166+
$configNode->setAllowUnnamedChildren($node->allowUnnamedChildren);
166167

167168
if (null !== $node->key) {
168169
$configNode->setKeyAttribute($node->key);

tests/Symfony/Tests/Component/Config/Definition/ArrayNodeTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,33 @@ public function testGetDefaultValueReturnsDefaultValueForPrototypes()
4949
$node->setDefaultValue(array ('test'));
5050
$this->assertEquals(array ('test'), $node->getDefaultValue());
5151
}
52+
53+
// finalizeValue() should protect against child values with no corresponding node
54+
public function testExceptionThrownOnUnrecognizedChild()
55+
{
56+
$this->setExpectedException('Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException');
57+
$node = new ArrayNode('root');
58+
$node->finalize(array('foo' => 'bar'));
59+
}
60+
61+
// if unnamedChildren is true, finalize allows them
62+
public function textNoExceptionForUnrecognizedChildWithUnnamedChildren()
63+
{
64+
$node = new ArrayNode('root');
65+
$node->setAllowUnnamedChildren(true);
66+
$finalized = $node->finalize(array('foo' => 'bar'));
67+
68+
$this->assertEquals(array('foo' => 'bar'), $finalized);
69+
}
70+
71+
/**
72+
* normalize() should not strip values that don't have children nodes.
73+
* Validation will take place later in finalizeValue().
74+
*/
75+
public function testNormalizeKeepsExtraArrayValues()
76+
{
77+
$node = new ArrayNode('root');
78+
$normalized = $node->normalize(array('foo' => 'bar'));
79+
$this->assertEquals(array('foo' => 'bar'), $normalized);
80+
}
5281
}

0 commit comments

Comments
 (0)