Skip to content

Commit

Permalink
[DependencyInjection] Initial implementation of an allowUnnamedChildr…
Browse files Browse the repository at this point in the history
…en 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.
  • Loading branch information
weaverryan committed Feb 18, 2011
1 parent 9b60262 commit f5b1cb1
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 3 deletions.
32 changes: 29 additions & 3 deletions src/Symfony/Component/Config/Definition/ArrayNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
protected $minNumberOfElements;
protected $performDeepMerging;
protected $defaultValue;
protected $allowUnnamedChildren;

/**
* Constructor.
Expand All @@ -52,6 +53,7 @@ public function __construct($name, NodeInterface $parent = null)
$this->allowNewKeys = true;
$this->performDeepMerging = true;
$this->minNumberOfElements = 0;
$this->allowUnnamedChildren = false;
}

/**
Expand Down Expand Up @@ -308,6 +310,14 @@ protected function finalizeValue($value)
}
}

// if extra fields are present and allowUnnamedChildren is false, throw exception
if (!$this->allowUnnamedChildren && $diff = array_diff(array_keys($value), array_keys($this->children))) {
$msg = sprintf('Unrecognized options "%s" under "%s"', implode(', ', $diff), $this->getPath());

throw new InvalidConfigurationException($msg);
}


return $value;
}

Expand Down Expand Up @@ -384,16 +394,17 @@ protected function normalizeValue($value)
return $normalized;
}

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

$normalized[$name] = $child->normalize($value[$name]);
$value[$name] = $child->normalize($value[$name]);
}

return $normalized;
return $value;
}

/**
Expand Down Expand Up @@ -452,4 +463,19 @@ protected function mergeValues($leftSide, $rightSide)

return $leftSide;
}

/**
* Set whether or not to allow this array to have child values that
* are not represented as nodes.
*
* An example would be an "options" array node, where its children
* could be any key of any form. In this case, no children are placed
* on the node, but child values must be allowed.
*
* @param Boolean $v Whether to allow unnamed children
*/
public function setAllowUnnamedChildren($v)
{
$this->allowUnnamedChildren = $v;
}
}
18 changes: 18 additions & 0 deletions src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class NodeBuilder
public $trueEquivalent;
public $falseEquivalent;
public $performDeepMerging;
public $allowUnnamedChildren;

/**
* Constructor
Expand All @@ -64,6 +65,7 @@ public function __construct($name, $type, $parent = null)
$this->allowEmptyValue = true;
$this->children = array();
$this->performDeepMerging = true;
$this->allowUnnamedChildren = false;

if ('boolean' === $type) {
$this->nullEquivalent = true;
Expand Down Expand Up @@ -440,4 +442,20 @@ public function end()
{
return $this->parent;
}

/**
* Allows child values not represented by a node.
*
* An example would be an "options" array node, where its children
* could be any key of any form. In this case, no children are placed
* on the node, but child values must be allowed.
*
* @return Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder
*/
public function allowUnnamedChildren()
{
$this->allowUnnamedChildren = true;

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ protected function createArrayConfigNode(NodeBuilder $node)
$configNode->addEquivalentValue(false, $node->falseEquivalent);
$configNode->setPerformDeepMerging($node->performDeepMerging);
$configNode->setRequired($node->required);
$configNode->setAllowUnnamedChildren($node->allowUnnamedChildren);

if (null !== $node->key) {
$configNode->setKeyAttribute($node->key);
Expand Down
29 changes: 29 additions & 0 deletions tests/Symfony/Tests/Component/Config/Definition/ArrayNodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,33 @@ public function testGetDefaultValueReturnsDefaultValueForPrototypes()
$node->setDefaultValue(array ('test'));
$this->assertEquals(array ('test'), $node->getDefaultValue());
}

// finalizeValue() should protect against child values with no corresponding node
public function testExceptionThrownOnUnrecognizedChild()
{
$this->setExpectedException('Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException');
$node = new ArrayNode('root');
$node->finalize(array('foo' => 'bar'));
}

// if unnamedChildren is true, finalize allows them
public function textNoExceptionForUnrecognizedChildWithUnnamedChildren()
{
$node = new ArrayNode('root');
$node->setAllowUnnamedChildren(true);
$finalized = $node->finalize(array('foo' => 'bar'));

$this->assertEquals(array('foo' => 'bar'), $finalized);
}

/**
* normalize() should not strip values that don't have children nodes.
* Validation will take place later in finalizeValue().
*/
public function testNormalizeKeepsExtraArrayValues()
{
$node = new ArrayNode('root');
$normalized = $node->normalize(array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $normalized);
}
}

0 comments on commit f5b1cb1

Please sign in to comment.