From bca2b0edf3ddbf1f4afc6903c2682d2f0ce67b3e Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 16 Feb 2012 13:55:25 +0100 Subject: [PATCH] [Config] Improve PrototypedArrayNode default value management --- .../Builder/ArrayNodeDefinition.php | 33 ++++++++- .../Config/Definition/PrototypedArrayNode.php | 24 +++++++ .../Builder/ArrayNodeDefinitionTest.php | 25 +++++++ .../Definition/PrototypedArrayNodeTest.php | 70 ++++++++++++++++++- 4 files changed, 148 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php index 669ced43a6fd..d979a8fe407e 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php @@ -31,6 +31,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition protected $key; protected $removeKeyItem; protected $addDefaults; + protected $addDefaultChildren; protected $nodeBuilder; /** @@ -42,6 +43,7 @@ public function __construct($name, NodeParentInterface $parent = null) $this->children = array(); $this->addDefaults = false; + $this->addDefaultChildren = false; $this->allowNewKeys = true; $this->atLeastOne = false; $this->allowEmptyValue = true; @@ -98,6 +100,22 @@ public function addDefaultsIfNotSet() return $this; } + /** + * Adds children with a default value when none are defined. + * + * @param integer|string|array $children The number of children|The child name|The children names to be added + * + * This method is applicable to prototype nodes only. + * + * @return ArrayNodeDefinition + */ + public function addDefaultChildrenWhenNoneSet($children = null) + { + $this->addDefaultChildren = null === $children ? 'defaults' : $children; + + return $this; + } + /** * Requires the node to have at least one element. * @@ -280,7 +298,7 @@ protected function createNode() $node = new ArrayNode($this->name, $this->parent); $node->setAddIfNotSet($this->addDefaults); - + foreach ($this->children as $child) { $child->parent = $node; $node->addChild($child->getNode()); @@ -292,6 +310,11 @@ protected function createNode() ); } + if ($this->default && false !== $this->addDefaultChildren) { + throw new InvalidDefinitionException('A default value and default children might not be used together.'); + + } + $node = new PrototypedArrayNode($this->name, $this->parent); if (null !== $this->key) { @@ -306,8 +329,16 @@ protected function createNode() $node->setDefaultValue($this->defaultValue); } + if (false !== $this->addDefaultChildren) { + $node->setAddChildrenIfNoneSet($this->addDefaultChildren); + if ($this->prototype instanceof static) { + $this->prototype->addDefaultsIfNotSet(); + } + } + $this->prototype->parent = $node; $node->setPrototype($this->prototype->getNode()); + } $node->setAllowNewKeys($this->allowNewKeys); diff --git a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php index e49d0d49f0ce..e8a52376ea9b 100644 --- a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +++ b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php @@ -28,6 +28,7 @@ class PrototypedArrayNode extends ArrayNode protected $removeKeyAttribute; protected $minNumberOfElements; protected $defaultValue; + protected $defaultChildren; /** * Constructor. @@ -120,13 +121,36 @@ public function hasDefaultValue() return true; } + /** + * Adds default children when none are set. + * + * @param integer|string|array $children The number of children|The child name|The children names to be added + */ + public function setAddChildrenIfNoneSet($children = array('defaults')) + { + $this->defaultChildren = is_integer($children) && $children > 0 ? range(1, $children) : (array) $children; + } + /** * Retrieves the default value. * + * The default value could be either explicited or derived from the prototype + * default value. + * * @return array The default value */ public function getDefaultValue() { + if (null !== $this->defaultChildren) { + $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array(); + $defaults = array(); + foreach (array_values($this->defaultChildren) as $i => $name) { + $defaults[null === $this->keyAttribute ? $i : $name] = $default; + } + + return $defaults; + } + return $this->defaultValue; } diff --git a/tests/Symfony/Tests/Component/Config/Definition/Builder/ArrayNodeDefinitionTest.php b/tests/Symfony/Tests/Component/Config/Definition/Builder/ArrayNodeDefinitionTest.php index 71ae7c865f4b..9639b94a2f3e 100644 --- a/tests/Symfony/Tests/Component/Config/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/tests/Symfony/Tests/Component/Config/Definition/Builder/ArrayNodeDefinitionTest.php @@ -64,6 +64,31 @@ public function testConcreteNodeSpecificOption() $node->getNode(); } + /** + * @expectedException Symfony\Component\Config\Definition\Exception\InvalidDefinitionException + */ + public function testPrototypeNodesCantHaveADefaultValueWhenUsingDefaulChildren() + { + $node = new ArrayNodeDefinition('root'); + $node + ->defaultValue(array()) + ->addDefaultChildrenWhenNoneSet('foo') + ->prototype('array') + ; + $node->getNode(); + } + + public function testArrayNodeDefaultWhenUsingDefaultChildren() + { + $node = new ArrayNodeDefinition('root'); + $node + ->addDefaultChildrenWhenNoneSet() + ->prototype('array') + ; + $tree = $node->getNode(); + $this->assertEquals(array(array()), $tree->getDefaultValue()); + } + protected function getField($object, $field) { $reflection = new \ReflectionProperty($object, $field); diff --git a/tests/Symfony/Tests/Component/Config/Definition/PrototypedArrayNodeTest.php b/tests/Symfony/Tests/Component/Config/Definition/PrototypedArrayNodeTest.php index 042e6671ac3f..4045b63b7824 100644 --- a/tests/Symfony/Tests/Component/Config/Definition/PrototypedArrayNodeTest.php +++ b/tests/Symfony/Tests/Component/Config/Definition/PrototypedArrayNodeTest.php @@ -43,7 +43,7 @@ public function testRemappedKeysAreUnset() $node->addChild($mappingsNode); // each item under mappings is just a scalar - $prototype= new ScalarNode(null, $mappingsNode); + $prototype = new ScalarNode(null, $mappingsNode); $mappingsNode->setPrototype($prototype); $remappings = array(); @@ -78,7 +78,7 @@ public function testMappedAttributeKeyIsRemoved() $node->setKeyAttribute('id', true); // each item under the root is an array, with one scalar item - $prototype= new ArrayNode(null, $node); + $prototype = new ArrayNode(null, $node); $prototype->addChild(new ScalarNode('foo')); $node->setPrototype($prototype); @@ -101,7 +101,7 @@ public function testMappedAttributeKeyNotRemoved() $node->setKeyAttribute('id', false); // each item under the root is an array, with two scalar items - $prototype= new ArrayNode(null, $node); + $prototype = new ArrayNode(null, $node); $prototype->addChild(new ScalarNode('foo')); $prototype->addChild(new ScalarNode('id')); // the key attribute will remain $node->setPrototype($prototype); @@ -114,4 +114,68 @@ public function testMappedAttributeKeyNotRemoved() $expected['item_name'] = array('id' => 'item_name', 'foo' => 'bar'); $this->assertEquals($expected, $normalized); } + + public function testAddDefaultChildren() + { + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaults' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet('defaultkey'); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(array('defaultkey')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setKeyAttribute('foobar'); + $node->setAddChildrenIfNoneSet(array('dk1', 'dk2')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array('dk1' => array('foo' => 'bar'), 'dk2' => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(array(5, 6)); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(0 => array('foo' => 'bar'), 1 => array('foo' => 'bar')), $node->getDefaultValue()); + + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(2); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar'), array('foo' => 'bar')), $node->getDefaultValue()); + } + + public function testDefaultChildrenWinsOverDefaultValue() + { + $node = $this->getPrototypeNodeWithDefaultChildren(); + $node->setAddChildrenIfNoneSet(); + $node->setDefaultValue(array('bar' => 'foo')); + $this->assertTrue($node->hasDefaultValue()); + $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue()); + } + + protected function getPrototypeNodeWithDefaultChildren() + { + $node = new PrototypedArrayNode('root'); + $prototype = new ArrayNode(null, $node); + $child = new ScalarNode('foo'); + $child->setDefaultValue('bar'); + $prototype->addChild($child); + $prototype->setAddIfNotSet(true); + $node->setPrototype($prototype); + + return $node; + } }