Преглед на файлове

[Config] Making the option to remove a key attribute optional.

This is *usually* what you want (and is defaulted this way). If you have an entry in an array *just* so it can become the key to that entry later, then you shouldn't normally still need it in the resulting array.

The importance of this comes in with validation. Since we're throwing an exception if you have any unrecognized options, the presence of the "key" field in the resulting array will cause issues when it's not needed.
Ryan Weaver преди 14 години
родител
ревизия
ea768fe6fc

+ 32 - 1
src/Symfony/Component/Config/Definition/ArrayNode.php

@@ -28,6 +28,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
     protected $children;
     protected $prototype;
     protected $keyAttribute;
+    protected $keyAttributeIsRemoved;
     protected $allowFalse;
     protected $allowNewKeys;
     protected $addIfNotSet;
@@ -48,6 +49,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
 
         $this->children = array();
         $this->xmlRemappings = array();
+        $this->keyAttributeIsRemoved = true;
         $this->allowFalse = false;
         $this->addIfNotSet = false;
         $this->allowNewKeys = true;
@@ -93,6 +95,31 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
         $this->keyAttribute = $attribute;
     }
 
+    /**
+     * Sets whether or not the key attribute should be removed from child items.
+     *
+     * If true (the default) and keyAttribute is set, then when a child item
+     * is remapped based off of the key attribute, the key attribute is removed
+     * from the item's value.
+     *
+     * In other words, if "id" is the keyAttribute, then:
+     *
+     *     array('id' => 'my_name', 'foo' => 'bar')
+     *
+     * becomes
+     *
+     *     'id' => array('foo' => 'bar')
+     *
+     * If false, the resulting array will still have the "'id' => 'my_name'"
+     * item in it.
+     *
+     * @param Boolean $remove Whether or not the key attribute should be removed.
+     */
+    public function setKeyAttributeIsRemoved($remove)
+    {
+        $this->keyAttributeIsRemoved = $remove;
+    }
+
     /**
      * Sets whether to add default values for this array if it has not been
      * defined in any of the configuration files.
@@ -365,7 +392,11 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
                         ));
                     } else if (isset($v[$this->keyAttribute])) {
                         $k = $v[$this->keyAttribute];
-                        unset($v[$this->keyAttribute]);
+
+                        // remove the key attribute if configured to
+                        if ($this->keyAttributeIsRemoved) {
+                            unset($v[$this->keyAttribute]);
+                        }
                     }
 
                     if (array_key_exists($k, $normalized)) {

+ 23 - 2
src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

@@ -24,6 +24,7 @@ class NodeBuilder
     public $name;
     public $type;
     public $key;
+    public $removeKeyItem;
     public $parent;
     public $children;
     public $prototype;
@@ -339,13 +340,33 @@ class NodeBuilder
     /**
      * Sets an attribute to use as key.
      *
-     * @param string $name
+     * This is useful when you have an indexed array that should be an
+     * associative array. You can select an item from within the array
+     * to be the key of the particular item. For example, if "id" is the
+     * "key", then:
+     *
+     *     array(
+     *         array('id' => 'my_name', 'foo' => 'bar'),
+     *     )
+     *
+     * becomes
+     *
+     *     array(
+     *         'id' => array('foo' => 'bar'),
+     *     )
+     *
+     * If you'd like "'id' => 'my_name'" to still be present in the resulting
+     * array, then you can set the second argument of this method to false.
+     *
+     * @param string $name The name of the key
+     * @param Boolean $removeKeyItem Whether or not the key item should be removed.
      *
      * @return Symfony\Component\Config\Definition\Builder\NodeBuilder
      */
-    public function useAttributeAsKey($name)
+    public function useAttributeAsKey($name, $removeKeyItem = true)
     {
         $this->key = $name;
+        $this->removeKeyItem = $removeKeyItem;
 
         return $this;
     }

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

@@ -167,6 +167,7 @@ class TreeBuilder
 
         if (null !== $node->key) {
             $configNode->setKeyAttribute($node->key);
+            $configNode->setKeyAttributeIsRemoved($node->removeKeyItem);
         }
 
         if (true === $node->atLeastOne) {

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

@@ -120,4 +120,27 @@ class ArrayNodeTest extends \PHPUnit_Framework_TestCase
         $expected['item_name'] = array('foo' => 'bar');
         $this->assertEquals($expected, $normalized);
     }
+
+    /**
+     * Tests the opposite of the testMappedAttributeKeyIsRemoved because
+     * the removal can be toggled with an option.
+     */
+    public function testMappedAttributeKeyNotRemoved()
+    {
+        $node = new ArrayNode('root');
+        $node->setKeyAttribute('id');
+        $node->setKeyAttributeIsRemoved(false);
+
+        $prototype = new ArrayNode(null);
+        $prototype->setPreventExtraKeys(false); // just so it allows anything
+        $node->setPrototype($prototype);
+
+        $children = array();
+        $children[] = array('id' => 'item_name', 'foo' => 'bar');
+        $normalized = $node->normalize($children);
+
+        $expected = array();
+        $expected['item_name'] = array('id' => 'item_name', 'foo' => 'bar');
+        $this->assertEquals($expected, $normalized);
+    }
 }