Forráskód Böngészése

[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.
Ryan Weaver 14 éve
szülő
commit
f5b1cb18e1

+ 29 - 3
src/Symfony/Component/Config/Definition/ArrayNode.php

@@ -34,6 +34,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
     protected $minNumberOfElements;
     protected $performDeepMerging;
     protected $defaultValue;
+    protected $allowUnnamedChildren;
 
     /**
      * Constructor.
@@ -52,6 +53,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
         $this->allowNewKeys = true;
         $this->performDeepMerging = true;
         $this->minNumberOfElements = 0;
+        $this->allowUnnamedChildren = false;
     }
 
     /**
@@ -308,6 +310,14 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
             }
         }
 
+        // 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;
     }
 
@@ -384,16 +394,17 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
             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;
     }
 
     /**
@@ -452,4 +463,19 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
 
         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 - 0
src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

@@ -42,6 +42,7 @@ class NodeBuilder
     public $trueEquivalent;
     public $falseEquivalent;
     public $performDeepMerging;
+    public $allowUnnamedChildren;
 
     /**
      * Constructor
@@ -64,6 +65,7 @@ class NodeBuilder
         $this->allowEmptyValue = true;
         $this->children = array();
         $this->performDeepMerging = true;
+        $this->allowUnnamedChildren = false;
 
         if ('boolean' === $type) {
             $this->nullEquivalent = true;
@@ -440,4 +442,20 @@ class NodeBuilder
     {
         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;
+    }
 }

+ 1 - 0
src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php

@@ -163,6 +163,7 @@ class TreeBuilder
         $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);

+ 29 - 0
tests/Symfony/Tests/Component/Config/Definition/ArrayNodeTest.php

@@ -49,4 +49,33 @@ class ArrayNodeTest extends \PHPUnit_Framework_TestCase
         $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);
+    }
 }