From 9fd7d05ecf439e2059455a7d70372c35ceaae3af Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 18 Mar 2011 00:26:03 +0100 Subject: [PATCH] [Config] Ability to add and override node types without having to subclass NodeBuilder --- .../Config/Definition/Builder/NodeBuilder.php | 53 +++++++++++-- .../Definition/Builder/NodeBuilderTest.php | 74 +++++++++++++++++++ .../Definition/Builder/TreeBuilderTest.php | 28 +++++-- 3 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 tests/Symfony/Tests/Component/Config/Definition/Builder/NodeBuilderTest.php diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php index c8073c29ed32..c6eb37622401 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php @@ -19,6 +19,21 @@ class NodeBuilder implements NodeParentInterface { protected $parent; + protected $nodeMapping; + + /** + * Constructor + * + */ + public function __construct() + { + $this->nodeMapping = array( + 'variable' => __NAMESPACE__.'\\VariableNodeDefinition', + 'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition', + 'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition', + 'array' => __NAMESPACE__.'\\ArrayNodeDefinition', + ); + } /** * Set the parent node @@ -100,16 +115,13 @@ public function end() * * @return NodeDefinition The child node * - * @throws \RuntimeException When the node type is not supported + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found */ public function node($name, $type) { $class = $this->getNodeClass($type); - if (!class_exists($class)) { - throw new \RuntimeException(sprintf('Unknown node type: "%s"', $type)); - } - $node = new $class($name); if ($node instanceof ParentNodeDefinitionInterface) { @@ -127,15 +139,44 @@ public function node($name, $type) return $node; } + /** + * Add or override a node Type + * + * @param string $type The name of the type + * @param string $class The fully qualified name the node definition class + */ + public function setNodeClass($type, $class) + { + $this->nodeMapping[strtolower($type)] = $class; + + return $this; + } + /** * Returns the class name of the node definition * * @param string $type The node type + * * @return string The node definition class name + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found */ protected function getNodeClass($type) { - return $class = __NAMESPACE__.'\\'.ucfirst($type).'NodeDefinition'; + $type = strtolower($type); + + if (!isset($this->nodeMapping[$type])) { + throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type)); + } + + $class = $this->nodeMapping[$type]; + + if (!class_exists($class)) { + throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class)); + } + + return $class; } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Config/Definition/Builder/NodeBuilderTest.php b/tests/Symfony/Tests/Component/Config/Definition/Builder/NodeBuilderTest.php new file mode 100644 index 000000000000..8d62a5a7170c --- /dev/null +++ b/tests/Symfony/Tests/Component/Config/Definition/Builder/NodeBuilderTest.php @@ -0,0 +1,74 @@ +node('', 'foobar'); + } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsAnExceptionWhenTheNodeClassIsNotFound() + { + $builder = new NodeBuilder(); + $builder + ->setNodeClass('noclasstype', '\\foo\\bar\\noclass') + ->node('', 'noclasstype'); + } + + public function testAddingANewNodeType() + { + $class = __NAMESPACE__.'\\SomeNodeDefinition'; + + $builder = new NodeBuilder(); + $node = $builder + ->setNodeClass('newtype', $class) + ->node('', 'newtype'); + + $this->assertEquals(get_class($node), $class); + } + + public function testOverridingAnExistingNodeType() + { + $class = __NAMESPACE__.'\\SomeNodeDefinition'; + + $builder = new NodeBuilder(); + $node = $builder + ->setNodeClass('variable', $class) + ->node('', 'variable'); + + $this->assertEquals(get_class($node), $class); + } + + public function testNodeTypesAreNotCaseSensitive() + { + $builder = new NodeBuilder(); + + $node1 = $builder->node('', 'VaRiAbLe'); + $node2 = $builder->node('', 'variable'); + + $this->assertEquals(get_class($node1), get_class($node2)); + + $builder->setNodeClass('CuStOm', __NAMESPACE__.'\\SomeNodeDefinition'); + + $node1 = $builder->node('', 'CUSTOM'); + $node2 = $builder->node('', 'custom'); + + $this->assertEquals(get_class($node1), get_class($node2)); + } +} + +class SomeNodeDefinition extends VariableNodeDefinition +{ +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Config/Definition/Builder/TreeBuilderTest.php b/tests/Symfony/Tests/Component/Config/Definition/Builder/TreeBuilderTest.php index b507a5dff1a0..12bf59b9d694 100644 --- a/tests/Symfony/Tests/Component/Config/Definition/Builder/TreeBuilderTest.php +++ b/tests/Symfony/Tests/Component/Config/Definition/Builder/TreeBuilderTest.php @@ -2,7 +2,9 @@ namespace Symfony\Tests\Component\Config\Definition\Builder; +use Symfony\Tests\Component\Config\Definition\Builder\NodeBuilder as CustomNodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; require __DIR__.'/../../Fixtures/Builder/NodeBuilder.php'; require __DIR__.'/../../Fixtures/Builder/BarNodeDefinition.php'; @@ -13,7 +15,7 @@ class TreeBuilderTest extends \PHPUnit_Framework_TestCase public function testUsingACustomNodeBuilder() { $builder = new TreeBuilder(); - $root = $builder->root('custom', 'array', new NodeBuilder()); + $root = $builder->root('custom', 'array', new CustomNodeBuilder()); $nodeBuilder = $root->children(); @@ -27,7 +29,7 @@ public function testUsingACustomNodeBuilder() public function testOverrideABuiltInNodeType() { $builder = new TreeBuilder(); - $root = $builder->root('override', 'array', new NodeBuilder()); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); $definition = $root->children()->variableNode('variable'); @@ -37,7 +39,7 @@ public function testOverrideABuiltInNodeType() public function testAddANodeType() { $builder = new TreeBuilder(); - $root = $builder->root('override', 'array', new NodeBuilder()); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); $definition = $root->children()->barNode('variable'); @@ -47,7 +49,7 @@ public function testAddANodeType() public function testCreateABuiltInNodeTypeWithACustomNodeBuilder() { $builder = new TreeBuilder(); - $root = $builder->root('builtin', 'array', new NodeBuilder()); + $root = $builder->root('builtin', 'array', new CustomNodeBuilder()); $definition = $root->children()->booleanNode('boolean'); @@ -57,9 +59,25 @@ public function testCreateABuiltInNodeTypeWithACustomNodeBuilder() public function testPrototypedArrayNodeUseTheCustomNodeBuilder() { $builder = new TreeBuilder(); - $root = $builder->root('override', 'array', new NodeBuilder()); + $root = $builder->root('override', 'array', new CustomNodeBuilder()); $root->prototype('bar')->end(); } + public function testAnExtendedNodeBuilderGetsPropagatedToTheChildren() + { + $builder = new TreeBuilder(); + + $builder->root('propagation') + ->children() + ->setNodeClass('extended', 'Symfony\Tests\Component\Config\Definition\Builder\VariableNodeDefinition') + ->node('foo', 'extended')->end() + ->arrayNode('child') + ->children() + ->node('foo', 'extended') + ->end() + ->end() + ->end() + ->end(); + } } \ No newline at end of file