Procházet zdrojové kódy

[DoctrineMongoDBBundle] Initial use of the new Configuration class for DoctrineMongoDBExtension.

Ryan Weaver před 14 roky
rodič
revize
a13500459f

+ 202 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/Configuration.php

@@ -0,0 +1,202 @@
+<?php
+
+namespace Symfony\Bundle\DoctrineMongoDBBundle\DependencyInjection;
+
+use Symfony\Component\Config\Definition\Builder\NodeBuilder;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+/**
+ * FrameworkExtension configuration structure.
+ *
+ * @author Ryan Weaver <ryan@thatsquality.com>
+ */
+class Configuration
+{
+    /**
+     * Generates the configuration tree.
+     *
+     * @param boolean $kernelDebug The kernel.debug DIC parameter
+     * @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface
+     */
+    public function getConfigTree()
+    {
+        $treeBuilder = new TreeBuilder();
+        $rootNode = $treeBuilder->root('doctrinemongodb', 'array');
+
+        $this->addSingleDocumentManagerSection($rootNode);
+        $this->addDocumentManagersSection($rootNode);
+        $this->addSingleConnectionSection($rootNode);
+        $this->addConnectionsSection($rootNode);
+
+        $rootNode
+            ->scalarNode('proxy_namespace')->defaultValue(null)->end()
+            ->scalarNode('auto_generate_proxy_classes')->defaultValue(null)->end()
+            ->scalarNode('hydrator_namespace')->defaultValue(null)->end()
+            ->scalarNode('auto_generate_hydrator_classes')->defaultValue(null)->end()
+        ;
+
+        return $treeBuilder->buildTree();
+    }
+
+    /**
+     * Builds the nodes responsible for the config that supports the single
+     * document manager.
+     */
+    private function addSingleDocumentManagerSection(NodeBuilder $rootNode)
+    {
+        $rootNode
+            ->scalarNode('default_document_manager')->defaultValue('default')->end()
+            ->scalarNode('default_database')->defaultValue('default')->end()
+            ->builder($this->getMetadataCacheDriverNode())
+            ->fixXmlConfig('mapping')
+            ->builder($this->getMappingsNode())
+        ;
+    }
+
+    /**
+     * Configures the "document_managers" section
+     */
+    private function addDocumentManagersSection(NodeBuilder $rootNode)
+    {
+        $rootNode
+            ->fixXmlConfig('document_manager')
+            ->arrayNode('document_managers')
+                ->useAttributeAsKey('id')
+                ->prototype('array')
+                    ->performNoDeepMerging()
+                    ->treatNullLike(array())
+                    ->builder($this->getMetadataCacheDriverNode())
+                    ->scalarNode('default_database')->end()
+                    ->scalarNode('connection')->end()
+                    ->scalarNode('database')->end()
+                    ->fixXmlConfig('mapping')
+                    ->builder($this->getMappingsNode())
+                ->end()
+            ->end()
+        ;
+    }
+
+    /**
+     * Configures the single-connection section:
+     *   * default_connection
+     *   * server
+     *   * options
+     */
+    private function addSingleConnectionSection(NodeBuilder $rootNode)
+    {
+        $rootNode
+            ->scalarNode('default_connection')->defaultValue('default')->end()
+            ->builder($this->addConnectionServerNode())
+            ->builder($this->addConnectionOptionsNode())
+        ;
+    }
+
+    /**
+     * Adds the configuration for the "connections" key
+     */
+    private function addConnectionsSection(NodeBuilder $rootNode)
+    {
+        $rootNode
+            ->fixXmlConfig('connection')
+            ->arrayNode('connections')
+                ->useAttributeAsKey('id')
+                ->prototype('array')
+                    ->performNoDeepMerging()
+                    ->builder($this->addConnectionServerNode())
+                    ->builder($this->addConnectionOptionsNode())
+                ->end()
+            ->end()
+        ;
+    }
+
+    /**
+     * Returns the array node used for "mappings".
+     *
+     * This is used in two different parts of the tree.
+     *
+     * @param NodeBuilder $rootNode The parent node
+     * @return NodeBuilder
+     */
+    protected function getMappingsNode()
+    {
+        $node = new Nodebuilder('mappings', 'array');
+        $node
+            ->useAttributeAsKey('name')
+            ->prototype('array')
+                // I believe that "null" should *not* set the type
+                // it's guessed in AbstractDoctrineExtension::detectMetadataDriver
+                ->treatNullLike(array())
+                ->beforeNormalization()
+                    // if it's not an array, then the scalar is the type key
+                    ->ifTrue(function($v) { return !is_array($v); })
+                    ->then(function($v){ return array('type' => $v); })
+                ->end()
+                ->scalarNode('type')->end()
+                ->scalarNode('dir')->end()
+                ->scalarNode('prefix')->end()
+                ->scalarNode('alias')->end()
+                ->performNoDeepMerging()
+            ->end()
+        ;
+
+        return $node;
+    }
+
+    /**
+     * Adds the NodeBuilder for the "server" key of a connection.
+     */
+    private function addConnectionServerNode()
+    {
+        $node = new NodeBuilder('server', 'scalar');
+
+        $node
+            ->defaultValue(null)
+        ->end();
+
+        return $node;
+    }
+
+    /**
+     * Adds the NodeBuilder for the "options" key of a connection.
+     */
+    private function addConnectionOptionsNode()
+    {
+        $node = new NodeBuilder('options', 'array');
+
+        $node
+            ->performNoDeepMerging()
+            ->addDefaultsIfNotSet() // adds an empty array of omitted
+
+            // options go into the Mongo constructor
+            // http://www.php.net/manual/en/mongo.construct.php
+            ->booleanNode('connect')->end()
+            ->scalarNode('persist')->end()
+            ->scalarNode('timeout')->end()
+            ->booleanNode('replicaSet')->end()
+            ->scalarNode('username')->end()
+            ->scalarNode('password')->end()
+        ->end();
+
+        return $node;
+    }
+
+    private function getMetadataCacheDriverNode()
+    {
+        $node = new NodeBuilder('metadata_cache_driver', 'array');
+
+        $node
+            ->beforeNormalization()
+                // if scalar
+                ->ifTrue(function($v) { return !is_array($v); })
+                ->then(function($v) { return array('type' => $v); })
+            ->end()
+            ->scalarNode('type')->end()
+            ->scalarNode('class')->end()
+            ->scalarNode('host')->end()
+            ->scalarNode('port')->end()
+            ->scalarNode('instance_class')->end()
+        ->end();
+
+        return $node;
+    }
+}

+ 15 - 35
src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php

@@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Definition;
 use Symfony\Component\Config\Resource\FileResource;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Bundle\DoctrineAbstractBundle\DependencyInjection\AbstractDoctrineExtension;
+use Symfony\Component\Config\Definition\Processor;
 
 /**
  * Doctrine MongoDB ODM extension.
@@ -60,23 +61,13 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
      */
     public function load(array $configs, ContainerBuilder $container)
     {
-        foreach ($configs as $config) {
-            $this->doMongodbLoad($config, $container);
-        }
-    }
+        // Load DoctrineMongoDBBundle/Resources/config/mongodb.xml
+        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+        $loader->load('mongodb.xml');
+        $processor = new Processor();
+        $configuration = new Configuration();
+        $config = $processor->process($configuration->getConfigTree(), $configs);
 
-    /**
-     * Loads the MongoDB ODM configuration.
-     *
-     * Usage example:
-     *
-     *     <doctrine:mongodb server="mongodb://localhost:27017" />
-     *
-     * @param array $config An array of configuration settings
-     * @param ContainerBuilder $container A ContainerBuilder instance
-     */
-    protected function doMongodbLoad($config, ContainerBuilder $container)
-    {
         $this->loadDefaults($config, $container);
         $this->loadConnections($config, $container);
         $this->loadDocumentManagers($config, $container);
@@ -91,17 +82,10 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
      */
     protected function loadDefaults(array $config, ContainerBuilder $container)
     {
-        if (!$container->hasDefinition('doctrine.odm.mongodb.metadata.annotation')) {
-            // Load DoctrineMongoDBBundle/Resources/config/mongodb.xml
-            $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
-            $loader->load('mongodb.xml');
-        }
-
         // Allow these application configuration options to override the defaults
         $options = array(
             'default_document_manager',
             'default_connection',
-            'metadata_cache_driver',
             'proxy_namespace',
             'auto_generate_proxy_classes',
             'hydrator_namespace',
@@ -112,11 +96,10 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
             if (isset($config[$key])) {
                 $container->setParameter('doctrine.odm.mongodb.'.$key, $config[$key]);
             }
+        }
 
-            $nKey = str_replace('_', '-', $key);
-            if (isset($config[$nKey])) {
-                $container->setParameter('doctrine.odm.mongodb.'.$key, $config[$nKey]);
-            }
+        if (isset($config['metadata_cache_driver'])) {
+            $container->setParameter('doctrine.odm.mongodb.metadata_cache_driver', $config['metadata_cache_driver']['type']);
         }
     }
 
@@ -222,11 +205,7 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
 
         $documentManagers = array();
 
-        if (isset($config['document-managers'])) {
-            $config['document_managers'] = $config['document-managers'];
-        }
-
-        if (isset($config['document_managers'])) {
+        if (count($config['document_managers'])) {
             $configDocumentManagers = $config['document_managers'];
 
             if (isset($config['document_managers']['document-manager'])) {
@@ -255,8 +234,8 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
     protected function loadDocumentManagerMetadataCacheDriver(array $documentManager, ContainerBuilder $container)
     {
         $metadataCacheDriver = $container->getParameter('doctrine.odm.mongodb.metadata_cache_driver');
-        $dmMetadataCacheDriver = isset($documentManager['metadata-cache-driver']) ? $documentManager['metadata-cache-driver'] : (isset($documentManager['metadata_cache_driver']) ? $documentManager['metadata_cache_driver'] : $metadataCacheDriver);
-        $type = is_array($dmMetadataCacheDriver) && isset($dmMetadataCacheDriver['type']) ? $dmMetadataCacheDriver['type'] : $dmMetadataCacheDriver;
+        $dmMetadataCacheDriver = isset($documentManager['metadata_cache_driver']) ? $documentManager['metadata_cache_driver'] : $metadataCacheDriver;
+        $type = is_array($dmMetadataCacheDriver) ? $dmMetadataCacheDriver['type'] : $dmMetadataCacheDriver;
 
         if ('memcache' === $type) {
             $memcacheClass = isset($dmMetadataCacheDriver['class']) ? $dmMetadataCacheDriver['class'] : sprintf('%%doctrine.odm.mongodb.cache.%s_class%%', $type);
@@ -271,6 +250,7 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
         } else {
              $cacheDef = new Definition(sprintf('%%doctrine.odm.mongodb.cache.%s_class%%', $type));
         }
+
         $container->setDefinition(sprintf('doctrine.odm.mongodb.%s_metadata_cache', $documentManager['name']), $cacheDef);
     }
 
@@ -305,7 +285,7 @@ class DoctrineMongoDBExtension extends AbstractDoctrineExtension
         $defaultConnection = $container->getParameter('doctrine.odm.mongodb.default_connection');
 
         $connections = array();
-        if (isset($config['connections'])) {
+        if (count($config['connections'])) {
             $configConnections = $config['connections'];
             if (isset($config['connections']['connection']) && isset($config['connections']['connection'][0])) {
                 // Multiple connections

+ 178 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/ConfigurationTest.php

@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Bundle\DoctrineMongoDBBundle\DependencyInjection\Configuration;
+
+use Symfony\Component\Config\Definition\Processor;
+
+class ConfigurationTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider optionProvider
+     * @param array $configs The source array of configuration arrays
+     * @param array $correctValues A key-value pair of end values to check
+     */
+    public function testMergeOptions(array $configs, array $correctValues)
+    {
+        $processor = new Processor();
+        $configuration = new Configuration();
+        $options = $processor->process($configuration->getConfigTree(), $configs);
+
+        foreach ($correctValues as $key => $correctVal)
+        {
+            $this->assertEquals($correctVal, $options[$key]);
+        }
+    }
+
+    public function optionProvider()
+    {
+        $cases = array();
+
+        // single config, testing normal option setting
+        $cases[] = array(
+            array(
+                array('default_document_manager' => 'foo'),
+            ),
+            array('default_document_manager' => 'foo')
+        );
+
+        // single config, testing normal option setting with dashes
+        $cases[] = array(
+            array(
+                array('default-document-manager' => 'bar'),
+            ),
+            array('default_document_manager' => 'bar')
+        );
+
+        // testing the normal override merging - the later config array wins
+        $cases[] = array(
+            array(
+                array('default_document_manager' => 'foo'),
+                array('default_document_manager' => 'baz'),
+            ),
+            array('default_document_manager' => 'baz')
+        );
+
+        // the "options" array is totally replaced
+        $cases[] = array(
+            array(
+                array('options' => array('timeout' => 2000)),
+                array('options' => array('username' => 'foo')),
+            ),
+            array('options' => array('username' => 'foo')),
+        );
+
+        // mappings are merged non-recursively.
+        $cases[] = array(
+            array(
+                array('mappings' => array('foomap' => array('type' => 'val1'), 'barmap' => array('dir' => 'val2'))),
+                array('mappings' => array('barmap' => array('prefix' => 'val3'))),
+            ),
+            array('mappings' => array('foomap' => array('type' => 'val1'), 'barmap' => array('prefix' => 'val3'))),
+        );
+
+        // connections are merged non-recursively.
+        $cases[] = array(
+            array(
+                array('connections' => array('foocon' => array('server' => 'val1'), 'barcon' => array('options' => array('username' => 'val2')))),
+                array('connections' => array('barcon' => array('server' => 'val3'))),
+            ),
+            array('connections' => array(
+                'foocon' => array('server' => 'val1', 'options' => array()),
+                'barcon' => array('server' => 'val3', 'options' => array())
+            )),
+        );
+
+        // managers are merged non-recursively.
+        $cases[] = array(
+            array(
+                array('document_managers' => array('foodm' => array('database' => 'val1'), 'bardm' => array('default_database' => 'val2'))),
+                array('document_managers' => array('bardm' => array('database' => 'val3'))),
+            ),
+            array('document_managers' => array(
+                'foodm' => array('database' => 'val1', 'mappings' => array()),
+                'bardm' => array('database' => 'val3', 'mappings' => array()),
+            )),
+        );
+
+        return $cases;
+    }
+
+    /**
+     * @dataProvider getNormalizationTests
+     */
+    public function testNormalizeOptions(array $config, $targetKey, array $normalized)
+    {
+        $processor = new Processor();
+        $configuration = new Configuration();
+        $options = $processor->process($configuration->getConfigTree(), array($config));
+        $this->assertSame($normalized, $options[$targetKey]);
+    }
+
+    public function getNormalizationTests()
+    {
+        return array(
+            // connection versus connections (id is the identifier)
+            array(
+                array('connection' => array(
+                    array('server' => 'mongodb://abc', 'id' => 'foo'),
+                    array('server' => 'mongodb://def', 'id' => 'bar'),
+                )),
+                'connections',
+                array(
+                    'foo' => array('server' => 'mongodb://abc', 'options' => array()),
+                    'bar' => array('server' => 'mongodb://def', 'options' => array()),
+                ),
+            ),
+            // document_manager versus document_managers (id is the identifier)
+            array(
+                array('document_manager' => array(
+                    array('connection' => 'conn1', 'id' => 'foo'),
+                    array('connection' => 'conn2', 'id' => 'bar'),
+                )),
+                'document_managers',
+                array(
+                    'foo' => array('connection' => 'conn1', 'mappings' => array()),
+                    'bar' => array('connection' => 'conn2', 'mappings' => array()),
+                ),
+            ),
+            // mapping versus mappings (name is the identifier)
+            array(
+                array('mapping' => array(
+                    array('type' => 'yml', 'name' => 'foo'),
+                    array('type' => 'xml', 'name' => 'bar'),
+                )),
+                'mappings',
+                array(
+                    'foo' => array('type' => 'yml'),
+                    'bar' => array('type' => 'xml'),
+                ),
+            ),
+            // mapping configuration that's beneath a specific document manager
+            array(
+                array('document_manager' => array(
+                    array('id' => 'foo', 'connection' => 'conn1', 'mapping' => array(
+                        'type' => 'xml', 'name' => 'foo-mapping'
+                    )),
+                )),
+                'document_managers',
+                array(
+                    'foo' => array('connection' => 'conn1', 'mappings' => array(
+                        'foo-mapping' => array('type' => 'xml'),
+                    )),
+                ),
+            ),
+        );
+    }
+}

+ 40 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/DoctrineMongoDBExtensionTest.php

@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Bundle\DoctrineMongoDBBundle\DependencyInjection\DoctrineMongoDBExtension;
+
+use Symfony\Component\Config\Definition\Processor;
+
+class DoctrineMongoDBExtensionTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider parameterProvider
+     */
+    public function testParameterOverride($option, $parameter, $value)
+    {
+        $container = new ContainerBuilder();
+        $loader = new DoctrineMongoDBExtension();
+        $loader->load(array(array($option => $value)), $container);
+
+        $this->assertEquals($value, $container->getParameter('doctrine.odm.mongodb.'.$parameter));
+    }
+
+    public function parameterProvider()
+    {
+        return array(
+            array('proxy_namespace', 'proxy_namespace', 'foo'),
+            array('proxy-namespace', 'proxy_namespace', 'bar'),
+        );
+    }
+}