浏览代码

[Config] Ability to add and override node types without having to subclass NodeBuilder

Victor Berchet 14 年之前
父节点
当前提交
9fd7d05ecf

+ 47 - 6
src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

@@ -19,6 +19,21 @@ namespace Symfony\Component\Config\Definition\Builder;
 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 @@ class NodeBuilder implements NodeParentInterface
      *
      * @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 @@ class NodeBuilder implements NodeParentInterface
         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;
     }
 
 }

+ 74 - 0
tests/Symfony/Tests/Component/Config/Definition/Builder/NodeBuilderTest.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Symfony\Tests\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Builder\NodeBuilder;
+use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition;
+
+class NodeBuilderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testThrowsAnExceptionWhenTryingToCreateANonRegisteredNodeType()
+    {
+        $builder = new NodeBuilder();
+        $builder->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
+{
+}

+ 23 - 5
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 @@ class TreeBuilderTest extends \PHPUnit_Framework_TestCase
     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 @@ class TreeBuilderTest extends \PHPUnit_Framework_TestCase
     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 @@ class TreeBuilderTest extends \PHPUnit_Framework_TestCase
     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 @@ class TreeBuilderTest extends \PHPUnit_Framework_TestCase
     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();
+    }
 }