diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000..21066d2ad9 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/Configuration.php @@ -0,0 +1,202 @@ + + */ +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; + } +} diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php index d778644b3c..53c2d1f81f 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php +++ b/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: - * - * - * - * @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 diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/ConfigurationTest.php new file mode 100644 index 0000000000..efe14f11d8 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -0,0 +1,178 @@ + + * + * 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'), + )), + ), + ), + ); + } +} \ No newline at end of file diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/DoctrineMongoDBExtensionTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/DoctrineMongoDBExtensionTest.php new file mode 100644 index 0000000000..e46181f34c --- /dev/null +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/DoctrineMongoDBExtensionTest.php @@ -0,0 +1,40 @@ + + * + * 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'), + ); + } +} \ No newline at end of file