diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5151325450022..6cad90e26cdc2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -24,7 +24,8 @@ ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources - ./src/Symfony/Component/HttpKernel/bootstrap* + ./src/Symfony/Component/HttpKernel/bootstrap.php + ./src/Symfony/Component/HttpKernel/bootstrap_cache.php diff --git a/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php b/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php index fbdb2663aa5fb..9e1158984b4f7 100644 --- a/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php +++ b/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php @@ -20,19 +20,4 @@ */ class CompatAssetsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php index 813c6033727a7..55fc875889407 100755 --- a/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php +++ b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php @@ -408,6 +408,7 @@ protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $ new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])) ); $ormEmDef = new Definition('%doctrine.orm.entity_manager_class%', $ormEmArgs); + $ormEmDef->setFactoryClass('%doctrine.orm.entity_manager_class%'); $ormEmDef->setFactoryMethod('create'); $ormEmDef->addTag('doctrine.orm.entity_manager'); $container->setDefinition($entityManagerService, $ormEmDef); diff --git a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php index 5e4e6ab21507e..de1d170543fde 100644 --- a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php @@ -31,20 +31,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index bd9fbefc9eaff..46a4e4046771b 100755 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -157,6 +157,7 @@ public function testDependencyInjectionConfigurationDefaults() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -198,6 +199,7 @@ public function testSingleEntityManagerConfiguration() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -239,6 +241,7 @@ public function testLoadSimpleSingleConnection() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -279,6 +282,7 @@ public function testLoadSingleConnection() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -313,6 +317,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.orm.dm1_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -334,6 +339,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.orm.dm2_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php index 3cd2803b1f05a..899d3e178eb0f 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } -} \ No newline at end of file +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php index 3e08c3a86e8b8..641edc122e105 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } -} \ No newline at end of file +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php index 9980c7bef12ea..3bdd9873eaa29 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php @@ -6,19 +6,4 @@ class XmlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php index 225ce0f90368f..c89784c34c4ac 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php @@ -6,19 +6,4 @@ class YamlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php b/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php index d987d30d79cda..6fadd42c11c35 100644 --- a/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php +++ b/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php @@ -21,19 +21,4 @@ */ class DoctrineMigrationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php index 880005e174879..0eb11749b3f48 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php @@ -165,6 +165,7 @@ protected function loadDocumentManager(array $documentManager, ContainerBuilder new Reference($eventManagerId), ); $odmDmDef = new Definition('%doctrine.odm.mongodb.document_manager_class%', $odmDmArgs); + $odmDmDef->setFactoryClass('%doctrine.odm.mongodb.document_manager_class%'); $odmDmDef->setFactoryMethod('create'); $odmDmDef->addTag('doctrine.odm.mongodb.document_manager'); $container->setDefinition(sprintf('doctrine.odm.mongodb.%s_document_manager', $documentManager['name']), $odmDmDef); diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php index d6d409c473b95..99e205e977a5f 100755 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php @@ -35,20 +35,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new CreateProxyDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new CreateHydratorDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php index b500b08e06350..dee13dabc7669 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php @@ -65,6 +65,7 @@ public function testDependencyInjectionConfigurationDefaults() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -92,6 +93,7 @@ public function testSingleDocumentManagerConfiguration() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -126,6 +128,7 @@ public function testLoadSimpleSingleConnection() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -154,6 +157,7 @@ public function testLoadSingleConnection() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -184,6 +188,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.odm.mongodb.dm1_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -199,6 +204,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.odm.mongodb.dm2_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php index 2c4eee1523a52..23b96a825d60f 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php index bdb28af65133b..f02676fcb8d6d 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php @@ -6,19 +6,4 @@ class XmlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php index 8c06610c5eb1e..3c63ccb389c15 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php @@ -6,19 +6,4 @@ class YamlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php index 3477454468d35..032b927804292 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php @@ -64,7 +64,7 @@ protected function computeTemplatePaths() $prefix = '/Resources/views'; $templates = array(); foreach ($this->kernel->getBundles() as $name => $bundle) { - if (!is_dir($dir = $bundle->getNormalizedPath().$prefix)) { + if (!is_dir($dir = $bundle->getPath().$prefix)) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..418a7544144cc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -0,0 +1,220 @@ + + */ +class Configuration +{ + /** + * Generates the configuration tree. + * + * @param boolean $kernelDebug The kernel.debug DIC parameter + * @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface + */ + public function getConfigTree($kernelDebug) + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('app:config', 'array'); + + $rootNode + ->scalarNode('cache_warmer')->defaultValue(!$kernelDebug)->end() + ->scalarNode('charset')->end() + ->scalarNode('document_root')->end() + ->scalarNode('error_handler')->end() + ->scalarNode('ide')->end() + ->booleanNode('test')->end() + ; + + $this->addCsrfProtectionSection($rootNode); + $this->addEsiSection($rootNode); + $this->addProfilerSection($rootNode); + $this->addRouterSection($rootNode); + $this->addSessionSection($rootNode); + $this->addTemplatingSection($rootNode); + $this->addTranslatorSection($rootNode); + $this->addValidationSection($rootNode); + + return $treeBuilder->buildTree(); + } + + private function addCsrfProtectionSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('csrf_protection') + ->canBeUnset() + ->treatNullLike(array('enabled' => true)) + ->treatTrueLike(array('enabled' => true)) + ->booleanNode('enabled')->end() + ->scalarNode('field_name')->end() + ->scalarNode('secret')->end() + ->end() + ; + } + + private function addEsiSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('esi') + ->canBeUnset() + ->treatNullLike(array('enabled' => true)) + ->treatTrueLike(array('enabled' => true)) + ->booleanNode('enabled')->end() + ->end() + ; + } + + private function addProfilerSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('profiler') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + ->booleanNode('only_exceptions')->end() + ->arrayNode('matcher') + ->canBeUnset() + ->scalarNode('ip')->end() + ->scalarNode('path')->end() + ->scalarNode('service')->end() + ->end() + ->end() + ; + } + + private function addRouterSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('router') + ->canBeUnset() + ->scalarNode('cache_warmer')->end() + ->scalarNode('resource')->isRequired()->end() + ->end() + ; + } + + private function addSessionSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('session') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + // Strip "pdo." prefix from option keys, since dots cannot appear in node names + ->beforeNormalization() + ->ifArray() + ->then(function($v){ + foreach ($v as $key => $value) { + if (0 === strncmp('pdo.', $key, 4)) { + $v[substr($key, 4)] = $value; + unset($v[$key]); + } + } + return $v; + }) + ->end() + ->booleanNode('auto_start')->end() + ->scalarNode('class')->end() + ->scalarNode('default_locale')->end() + ->scalarNode('storage_id')->defaultValue('native')->end() + // NativeSessionStorage options + ->scalarNode('name')->end() + ->scalarNode('lifetime')->end() + ->scalarNode('path')->end() + ->scalarNode('domain')->end() + ->booleanNode('secure')->end() + ->booleanNode('httponly')->end() + // PdoSessionStorage options + ->scalarNode('db_table')->end() + ->scalarNode('db_id_col')->end() + ->scalarNode('db_data_col')->end() + ->scalarNode('db_time_col')->end() + ->end() + ; + } + + private function addTemplatingSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('templating') + ->canBeUnset() + ->scalarNode('assets_version')->end() + ->scalarNode('assets_base_urls')->end() + ->scalarNode('cache')->end() + ->scalarNode('cache_warmer')->end() + ->fixXmlConfig('engine') + ->arrayNode('engines') + ->requiresAtLeastOneElement() + ->beforeNormalization() + ->ifTrue(function($v){ return !is_array($v); }) + ->then(function($v){ return array($v); }) + ->end() + ->prototype('scalar') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['id']); }) + ->then(function($v){ return $v['id']; }) + ->end() + ->end() + ->end() + ->fixXmlConfig('loader') + ->arrayNode('loaders') + ->beforeNormalization() + ->ifTrue(function($v){ return !is_array($v); }) + ->then(function($v){ return array($v); }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ; + } + + private function addTranslatorSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('translator') + ->canBeUnset() + ->booleanNode('enabled')->defaultTrue()->end() + ->scalarNode('fallback')->end() + ->end() + ; + } + + private function addValidationSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('validation') + ->canBeUnset() + // For XML, namespace is a child of validation, so it must be moved under annotations + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && !empty($v['annotations']) && !empty($v['namespace']); }) + ->then(function($v){ + $v['annotations'] = array('namespace' => $v['namespace']); + return $v; + }) + ->end() + ->booleanNode('enabled')->end() + ->arrayNode('annotations') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + ->fixXmlConfig('namespace') + ->arrayNode('namespaces') + ->containsNameValuePairsWithKeyAttribute('prefix') + ->prototype('scalar') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['namespace']); }) + ->then(function($v){ return $v['namespace']; }) + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ecbeae5bb72d7..411835221d3dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -11,151 +11,119 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Configuration\Processor; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Form\FormContext; /** * FrameworkExtension. * * @author Fabien Potencier + * @author Jeremy Mikola */ class FrameworkExtension extends Extension { - public function configLoad(array $configs, ContainerBuilder $container) - { - foreach ($configs as $config) { - $this->doConfigLoad($config, $container); - } - } - /** - * Loads the web configuration. + * Responds to the app.config configuration parameter. * - * @param array $config An array of configuration settings - * @param ContainerBuilder $container A ContainerBuilder instance + * @param array $configs + * @param ContainerBuilder $container */ - protected function doConfigLoad(array $config, ContainerBuilder $container) + public function configLoad(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - if (!$container->hasDefinition('controller_resolver')) { - $loader->load('web.xml'); - } + $loader->load('web.xml'); + $loader->load('form.xml'); + $loader->load('services.xml'); - if (!$container->hasDefinition('form.factory')) { - $loader->load('form.xml'); - } + // A translator must always be registered (as support is included by + // default in the Form component). If disabled, an identity translator + // will be used and everything will still work as expected. + $loader->load('translation.xml'); - if (isset($config['csrf-protection'])) { - $config['csrf_protection'] = $config['csrf-protection']; + if ($container->getParameter('kernel.debug')) { + $loader->load('debug.xml'); + $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher')); + $container->setAlias('debug.event_dispatcher', 'event_dispatcher'); } - if (isset($config['csrf_protection'])) { - foreach (array('enabled', 'field_name', 'field-name', 'secret') as $key) { - if (isset($config['csrf_protection'][$key])) { - $container->setParameter('form.csrf_protection.'.strtr($key, '-', '_'), - $config['csrf_protection'][$key]); - } - } - } + $processor = new Processor(); + $configuration = new Configuration(); - if (isset($config['ide'])) { - switch ($config['ide']) { - case 'textmate': - $pattern = 'txmt://open?url=file://%%f&line=%%l'; - break; - - case 'macvim': - $pattern = 'mvim://open?url=file://%%f&line=%%l'; - break; - - default: - // should be the link pattern then - $pattern = $config['ide']; - } + $config = $processor->process($configuration->getConfigTree($container->getParameter('kernel.debug')), $configs); - $container->setParameter('debug.file_link_format', $pattern); - } + $container->setParameter('kernel.cache_warmup', $config['cache_warmer']); - foreach (array('document_root', 'document-root') as $key) { - if (isset($config[$key])) { - $container->setParameter('document_root', $config[$key]); - } + if (isset($config['charset'])) { + $container->setParameter('kernel.charset', $config['charset']); } - if (!$container->hasDefinition('event_dispatcher')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('services.xml'); + if (isset($config['document_root'])) { + $container->setParameter('document_root', $config['document_root']); + } - if ($container->getParameter('kernel.debug')) { - $loader->load('debug.xml'); - $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher')); - $container->setAlias('debug.event_dispatcher', 'event_dispatcher'); + if (isset($config['error_handler'])) { + if (false === $config['error_handler']) { + $container->getDefinition('error_handler')->setMethodCalls(array()); + } else { + $container->getDefinition('error_handler')->addMethodCall('register', array()); + $container->setParameter('error_handler.level', $config['error_handler']); } } - if (isset($config['charset'])) { - $container->setParameter('kernel.charset', $config['charset']); + if (isset($config['ide'])) { + $patterns = array( + 'textmate' => 'txmt://open?url=file://%%f&line=%%l', + 'macvim' => 'mvim://open?url=file://%%f&line=%%l', + ); + $pattern = isset($patterns[$config['ide']]) ? $patterns[$config['ide']] : $config['ide']; + $container->setParameter('debug.file_link_format', $pattern); } - foreach (array('error_handler', 'error-handler') as $key) { - if (array_key_exists($key, $config)) { - if (false === $config[$key]) { - $container->getDefinition('error_handler')->setMethodCalls(array()); - } else { - $container->getDefinition('error_handler')->addMethodCall('register', array()); - $container->setParameter('error_handler.level', $config[$key]); - } - } + if (isset($config['test']) && $config['test']) { + $loader->load('test.xml'); + $config['session']['storage_id'] = 'array'; } - if (isset($config['router'])) { - $this->registerRouterConfiguration($config, $container); + if (isset($config['csrf_protection'])) { + $this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container); } - if (isset($config['profiler'])) { - $this->registerProfilerConfiguration($config, $container); + if (isset($config['esi'])) { + $this->registerEsiConfiguration($config['esi'], $loader); } - if (isset($config['validation']['enabled'])) { - $this->registerValidationConfiguration($config, $container); + if (isset($config['profiler'])) { + $this->registerProfilerConfiguration($config['profiler'], $container, $loader); } - if (array_key_exists('templating', $config)) { - $this->registerTemplatingConfiguration($config, $container); + if (isset($config['router'])) { + $this->registerRouterConfiguration($config['router'], $container, $loader); } - if (array_key_exists('test', $config)) { - $this->registerTestConfiguration($config, $container); + if (isset($config['session'])) { + $this->registerSessionConfiguration($config['session'], $container, $loader); } - if (array_key_exists('session', $config)) { - $this->registerSessionConfiguration($config, $container); + if (isset($config['templating'])) { + $this->registerTemplatingConfiguration($config['templating'], $container, $loader); } - // translator must always be registered (as support is included by default for forms for instance) - // if you disable it, an identity translator will be used and everything will still work as expected - $this->registerTranslatorConfiguration($config, $container); - - if (array_key_exists('esi', $config)) { - $this->registerEsiConfiguration($config, $container); + if (isset($config['translator'])) { + $this->registerTranslatorConfiguration($config['translator'], $container); } - if (isset($config['cache-warmer'])) { - $config['cache_warmer'] = $config['cache-warmer']; + if (isset($config['validation'])) { + $this->registerValidationConfiguration($config['validation'], $container, $loader); } - $warmer = isset($config['cache_warmer']) ? $config['cache_warmer'] : !$container->getParameter('kernel.debug'); - $container->setParameter('kernel.cache_warmup', $warmer); - $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\ParameterBag', 'Symfony\\Component\\HttpFoundation\\HeaderBag', @@ -178,235 +146,114 @@ protected function doConfigLoad(array $config, ContainerBuilder $container) 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface', 'Symfony\\Component\\EventDispatcher\\EventDispatcher', 'Symfony\\Bundle\\FrameworkBundle\\EventDispatcher', - - 'Symfony\\Component\\Form\\FormContext', - 'Symfony\\Component\\Form\\FormContextInterface', )); } /** - * Loads the templating configuration. + * Loads the CSRF protection configuration. * - * @param array $config An array of configuration settings + * @param array $config A CSRF protection configuration array * @param ContainerBuilder $container A ContainerBuilder instance */ - protected function registerTemplatingConfiguration(array $config, ContainerBuilder $container) + private function registerCsrfProtectionConfiguration(array $config, ContainerBuilder $container) { - $config = isset($config['templating']) ? $config['templating'] : array(); - - if (!$container->hasDefinition('templating.locator')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('templating.xml'); - $loader->load('templating_php.xml'); - - if ($container->getParameter('kernel.debug')) { - $loader->load('templating_debug.xml'); - } - } - - if (array_key_exists('assets-version', $config)) { - $container->setParameter('templating.assets.version', $config['assets-version']); - } - - if (array_key_exists('assets_version', $config)) { - $container->setParameter('templating.assets.version', $config['assets_version']); - } - - if (array_key_exists('assets-base-urls', $config)) { - $container->setParameter('templating.assets.base_urls', $config['assets-base-urls']); - } - - if (array_key_exists('assets_base_urls', $config)) { - $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); - } - - // loaders - if (isset($config['loader'])) { - $loaders = array(); - $ids = is_array($config['loader']) ? $config['loader'] : array($config['loader']); - foreach ($ids as $id) { - $loaders[] = new Reference($id); - } - - if (1 === count($loaders)) { - $container->setAlias('templating.loader', (string) $loaders[0]); - } else { - $container->getDefinition('templating.loader.chain')->addArgument($loaders); - $container->setAlias('templating.loader', 'templating.loader.chain'); - } - } - - // cache? - $container->setParameter('templating.loader.cache.path', null); - if (isset($config['cache'])) { - // wrap the loader with some cache - $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); - $container->setDefinition('templating.loader', $container->getDefinition('templating.loader.cache')); - $container->setParameter('templating.loader.cache.path', $config['cache']); - } - - if (isset($config['cache-warmer'])) { - $config['cache_warmer'] = $config['cache-warmer']; - } - - if (isset($config['cache_warmer']) && $config['cache_warmer']) { - $container->getDefinition('templating.cache_warmer.template_paths')->addTag('kernel.cache_warmer'); - $container->setAlias('templating.locator', 'templating.locator.cached'); - } - - // engines - if (!$engines = $this->normalizeConfig($config, 'engine')) { - throw new \LogicException('You must register at least one templating engine.'); - } - - $this->addClassesToCompile(array( - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface', - 'Symfony\\Component\\Templating\\EngineInterface', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocatorInterface', - $container->findDefinition('templating.locator')->getClass(), - )); - - foreach ($engines as $i => $engine) { - $id = is_array($engine) ? $engine['id'] : $engine; - $engines[$i] = new Reference('templating.engine.'.$id); - - if ('php' === $id) { - $this->addClassesToCompile(array( - 'Symfony\\Component\\Templating\\PhpEngine', - 'Symfony\\Component\\Templating\\TemplateNameParserInterface', - 'Symfony\\Component\\Templating\\TemplateNameParser', - 'Symfony\\Component\\Templating\\Loader\\LoaderInterface', - 'Symfony\\Component\\Templating\\Storage\\Storage', - 'Symfony\\Component\\Templating\\Storage\\FileStorage', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', - )); + foreach (array('enabled', 'field_name', 'secret') as $key) { + if (isset($config[$key])) { + $container->setParameter('form.csrf_protection.'.$key, $config[$key]); } } - - if (1 === count($engines)) { - $container->setAlias('templating', (string) $engines[0]); - } else { - $def = $container->getDefinition('templating.engine.delegating'); - $def->setArgument(1, $engines); - - $container->setAlias('templating', 'templating.engine.delegating'); - } - } - - /** - * Loads the test configuration. - * - * @param array $config A configuration array - * @param ContainerBuilder $container A ContainerBuilder instance - */ - protected function registerTestConfiguration(array $config, ContainerBuilder $container) - { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('test.xml'); - - $container->setAlias('session.storage', 'session.storage.array'); } /** * Loads the ESI configuration. * - * @param array $config A configuration array - * @param ContainerBuilder $container A ContainerBuilder instance + * @param array $config An ESI configuration array + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerEsiConfiguration(array $config, ContainerBuilder $container) + private function registerEsiConfiguration(array $config, XmlFileLoader $loader) { - if (isset($config['esi']['enabled']) && $config['esi']['enabled']) { - if (!$container->hasDefinition('esi')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('esi.xml'); - } + if (isset($config['enabled']) && $config['enabled']) { + $loader->load('esi.xml'); } } /** - * Loads the translator configuration. + * Loads the profiler configuration. * - * @param array $config A configuration array + * @param array $config A profiler configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerTranslatorConfiguration(array $config, ContainerBuilder $container) + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - $first = false; - if (!$container->hasDefinition('translator')) { - $first = true; - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('translation.xml'); - } + $loader->load('profiling.xml'); + $loader->load('collectors.xml'); - $config = array_key_exists('translator', $config) ? $config['translator'] : array(); - if (!is_array($config)) { - $config = array(); + if (isset($config['only_exceptions'])) { + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); } - if (!isset($config['translator']['enabled']) || $config['translator']['enabled']) { - // use the "real" translator - $container->setDefinition('translator', $container->findDefinition('translator.real')); + if (isset($config['matcher'])) { + if (isset($config['matcher']['service'])) { + $container->setAlias('profiler.request_matcher', $config['matcher']['service']); + } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { + $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); + $definition->setPublic(false); - if ($first) { - // translation directories - $dirs = array(); - foreach ($container->getParameter('kernel.bundles') as $bundle) { - $reflection = new \ReflectionClass($bundle); - if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { - $dirs[] = $dir; - } - } - if (is_dir($dir = $container->getParameter('kernel.root_dir').'/translations')) { - $dirs[] = $dir; + if (isset($config['matcher']['ip'])) { + $definition->addMethodCall('matchIp', array($config['matcher']['ip'])); } - // translation resources - $resources = array(); - if ($dirs) { - $finder = new Finder(); - $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs); - foreach ($finder as $file) { - // filename is domain.locale.format - list($domain, $locale, $format) = explode('.', $file->getBasename()); - - $resources[] = array($format, (string) $file, $locale, $domain); - } + if (isset($config['matcher']['path'])) { + $definition->addMethodCall('matchPath', array($config['matcher']['path'])); } - $container->setParameter('translation.resources', $resources); } } - - if (array_key_exists('fallback', $config)) { - $container->setParameter('translator.fallback_locale', $config['fallback']); - } } /** - * Loads the session configuration. + * Loads the router configuration. * - * @param array $config A configuration array + * @param array $config A router configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + * @throws \InvalidArgumentException if resource option is not set */ - protected function registerSessionConfiguration(array $config, ContainerBuilder $container) + private function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!$container->hasDefinition('session')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('session.xml'); + $loader->load('routing.xml'); + + if (!isset($config['resource'])) { + throw new \InvalidArgumentException('Router configuration requires a resource option.'); } - $config = isset($config['session']) ? $config['session'] : array(); + $container->setParameter('routing.resource', $config['resource']); - foreach (array('default_locale', 'default-locale') as $key) { - if (isset($config[$key])) { - $container->setParameter('session.default_locale', $config[$key]); - } + if (isset($config['cache_warmer']) && $config['cache_warmer']) { + $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); + $container->setAlias('router', 'router.cached'); } - if (isset($config['auto-start'])) { - $config['auto_start'] = $config['auto-start']; - } + $this->addClassesToCompile(array( + 'Symfony\\Component\\Routing\\RouterInterface', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface', + 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + $container->findDefinition('router')->getClass(), + )); + } + + /** + * Loads the session configuration. + * + * @param array $config A session configuration array + * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + */ + private function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + $loader->load('session.xml'); if (isset($config['auto_start']) && $config['auto_start']) { $container->getDefinition('session')->addMethodCall('start'); @@ -416,29 +263,19 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder $container->setParameter('session.class', $config['class']); } - if (isset($config['storage-id'])) { - $config['storage_id'] = $config['storage-id']; + if (isset($config['default_locale'])) { + $container->setParameter('session.default_locale', $config['default_locale']); } - if (isset($config['storage_id'])) { - $container->setAlias('session.storage', 'session.storage.'.$config['storage_id']); - } else { - $config['storage_id'] = 'native'; - } - - $options = $container->getParameter('session.storage.'.strtolower($config['storage_id']).'.options'); - foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'cache_limiter', 'pdo.db_table', 'pdo.db_id_col', 'pdo.db_data_col', 'pdo.db_time_col') as $name) { - $key = str_replace('pdo.', '', $name); - if (isset($config[$name])) { - $options[$key] = $config[$name]; - } + $container->setAlias('session.storage', 'session.storage.'.$config['storage_id']); - $nName = str_replace('_', '-', $name); - if (isset($config[$nName])) { - $options[$key] = $config[$nName]; + $options = $container->getParameter('session.storage.'.$config['storage_id'].'.options'); + foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'db_table', 'db_id_col', 'db_data_col', 'db_time_col') as $key) { + if (isset($config[$key])) { + $options[$key] = $config[$key]; } } - $container->setParameter('session.storage.'.strtolower($config['storage_id']).'.options', $options); + $container->setParameter('session.storage.'.$config['storage_id'].'.options', $options); $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\Session', @@ -448,167 +285,204 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder } /** - * Loads the router configuration. + * Loads the templating configuration. * - * @param array $config A configuration array + * @param array $config A templating configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + * @throws \LogicException if no engines are defined */ - protected function registerRouterConfiguration(array $config, ContainerBuilder $container) + private function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!$container->hasDefinition('router')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('routing.xml'); + $loader->load('templating.xml'); + $loader->load('templating_php.xml'); + + if ($container->getParameter('kernel.debug')) { + $loader->load('templating_debug.xml'); } - $container->setParameter('routing.resource', $config['router']['resource']); + if (isset($config['assets_version'])) { + $container->setParameter('templating.assets.version', $config['assets_version']); + } - if (isset($config['router']['cache-warmer'])) { - $config['router']['cache_warmer'] = $config['router']['cache-warmer']; + if (isset($config['assets_base_urls'])) { + $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); } - if (isset($config['router']['cache_warmer']) && $config['router']['cache_warmer']) { - $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); - $container->setAlias('router', 'router.cached'); + if (isset($config['loaders'])) { + $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']); + + // Use a deligation unless only a single loader was registered + if (1 === count($loaders)) { + $container->setAlias('templating.loader', (string) reset($loaders)); + } else { + $container->getDefinition('templating.loader.chain')->addArgument($loaders); + $container->setAlias('templating.loader', 'templating.loader.chain'); + } + } + + if (isset($config['cache'])) { + // Wrap the existing loader with cache (must happen after loaders are registered) + $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); + $container->setDefinition('templating.loader', $container->getDefinition('templating.loader.cache')); + $container->setParameter('templating.loader.cache.path', $config['cache']); + } else { + $container->setParameter('templating.loader.cache.path', null); + } + + if (isset($config['cache_warmer'])) { + $container->getDefinition('templating.cache_warmer.template_paths')->addTag('kernel.cache_warmer'); + $container->setAlias('templating.locator', 'templating.locator.cached'); + } + + if (empty($config['engines'])) { + throw new \LogicException('You must register at least one templating engine.'); } $this->addClassesToCompile(array( - 'Symfony\\Component\\Routing\\RouterInterface', - 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface', - 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface', - 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - $container->findDefinition('router')->getClass() + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface', + 'Symfony\\Component\\Templating\\EngineInterface', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocatorInterface', + $container->findDefinition('templating.locator')->getClass(), )); + + if (in_array('php', $config['engines'], true)) { + $this->addClassesToCompile(array( + 'Symfony\\Component\\Templating\\PhpEngine', + 'Symfony\\Component\\Templating\\TemplateNameParserInterface', + 'Symfony\\Component\\Templating\\TemplateNameParser', + 'Symfony\\Component\\Templating\\Loader\\LoaderInterface', + 'Symfony\\Component\\Templating\\Storage\\Storage', + 'Symfony\\Component\\Templating\\Storage\\FileStorage', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', + )); + } + + $engines = array_map(function($engine) { return new Reference('templating.engine.'.$engine); }, $config['engines']); + + // Use a deligation unless only a single engine was registered + if (1 === count($engines)) { + $container->setAlias('templating', (string) reset($engines)); + } else { + $container->getDefinition('templating.engine.delegating')->setArgument(1, $engines); + $container->setAlias('templating', 'templating.engine.delegating'); + } } /** - * Loads the profiler configuration. - * - * - * - * - * - * - * - * + * Loads the translator configuration. * - * @param array $config A configuration array + * @param array $config A translator configuration array * @param ContainerBuilder $container A ContainerBuilder instance */ - protected function registerProfilerConfiguration(array $config, ContainerBuilder $container) + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container) { - if ($config['profiler']) { - if (!$container->hasDefinition('profiler')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('profiling.xml'); - $loader->load('collectors.xml'); - } + if (isset($config['enabled']) && $config['enabled']) { + // Use the "real" translator instead of the identity default + $container->setDefinition('translator', $container->findDefinition('translator.real')); - if (isset($config['profiler']['only-exceptions'])) { - $container->setParameter('profiler_listener.only_exceptions', $config['profiler']['only-exceptions']); - } elseif (isset($config['profiler']['only_exceptions'])) { - $container->setParameter('profiler_listener.only_exceptions', $config['profiler']['only_exceptions']); + // Discover translation directories + $dirs = array(); + foreach ($container->getParameter('kernel.bundles') as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { + $dirs[] = $dir; + } + } + if (is_dir($dir = $container->getParameter('kernel.root_dir').'/translations')) { + $dirs[] = $dir; } - if (isset($config['profiler']['matcher'])) { - if (isset($config['profiler']['matcher']['service'])) { - $container->setAlias('profiler.request_matcher', $config['profiler']['matcher']['service']); - } elseif (isset($config['profiler']['matcher']['_services'])) { - $container->setAlias('profiler.request_matcher', (string) $config['profiler']['matcher']['_services'][0]); - } else { - $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); - $definition->setPublic(false); - - if (isset($config['profiler']['matcher']['ip'])) { - $definition->addMethodCall('matchIp', array($config['profiler']['matcher']['ip'])); - } - - if (isset($config['profiler']['matcher']['path'])) { - $definition->addMethodCall('matchPath', array($config['profiler']['matcher']['path'])); - } + // Register translation resources + $resources = array(); + if ($dirs) { + $finder = new Finder(); + $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs); + foreach ($finder as $file) { + // filename is domain.locale.format + list($domain, $locale, $format) = explode('.', $file->getBasename()); + + $resources[] = array($format, (string) $file, $locale, $domain); } - } else { - $container->removeAlias('profiler.request_matcher'); } - } elseif ($container->hasDefinition('profiler')) { - $container->getDefinition('profiling')->clearTags(); + $container->setParameter('translation.resources', $resources); + } + + if (isset($config['fallback'])) { + $container->setParameter('translator.fallback_locale', $config['fallback']); } } /** * Loads the validator configuration. * - * @param array $config A configuration array + * @param array $config A validation configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerValidationConfiguration(array $config, ContainerBuilder $container) + private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if ($config['validation']['enabled']) { - if (!$container->hasDefinition('validator')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('validator.xml'); - } + if (empty($config['enabled'])) { + return; + } - $xmlMappingFiles = array(); - $yamlMappingFiles = array(); + $loader->load('validator.xml'); - // default entries by the framework - $xmlMappingFiles[] = __DIR__.'/../../../Component/Form/Resources/config/validation.xml'; + $xmlMappingFiles = array(); + $yamlMappingFiles = array(); - foreach ($container->getParameter('kernel.bundles') as $bundle) { - $reflection = new \ReflectionClass($bundle); - if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.xml')) { - $xmlMappingFiles[] = realpath($file); - } - if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.yml')) { - $yamlMappingFiles[] = realpath($file); - } + // Include default entries from the framework + $xmlMappingFiles[] = __DIR__.'/../../../Component/Form/Resources/config/validation.xml'; + + foreach ($container->getParameter('kernel.bundles') as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.xml')) { + $xmlMappingFiles[] = realpath($file); } + if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.yml')) { + $yamlMappingFiles[] = realpath($file); + } + } - $xmlFilesLoader = new Definition( - $container->getParameter('validator.mapping.loader.xml_files_loader.class'), - array($xmlMappingFiles) - ); - $xmlFilesLoader->setPublic(false); + $xmlFilesLoader = new Definition('%validator.mapping.loader.xml_files_loader.class%', array($xmlMappingFiles)); + $xmlFilesLoader->setPublic(false); - $yamlFilesLoader = new Definition( - $container->getParameter('validator.mapping.loader.yaml_files_loader.class'), - array($yamlMappingFiles) - ); - $yamlFilesLoader->setPublic(false); + $yamlFilesLoader = new Definition('%validator.mapping.loader.yaml_files_loader.class%', array($yamlMappingFiles)); + $yamlFilesLoader->setPublic(false); - $container->setDefinition('validator.mapping.loader.xml_files_loader', $xmlFilesLoader); - $container->setDefinition('validator.mapping.loader.yaml_files_loader', $yamlFilesLoader); + $container->setDefinition('validator.mapping.loader.xml_files_loader', $xmlFilesLoader); + $container->setDefinition('validator.mapping.loader.yaml_files_loader', $yamlFilesLoader); - foreach ($xmlMappingFiles as $file) { - $container->addResource(new FileResource($file)); - } + foreach ($xmlMappingFiles as $file) { + $container->addResource(new FileResource($file)); + } - foreach ($yamlMappingFiles as $file) { - $container->addResource(new FileResource($file)); - } + foreach ($yamlMappingFiles as $file) { + $container->addResource(new FileResource($file)); + } - if (isset($config['validation']['annotations'])) { - if (isset($config['validation']['annotations']['namespaces']) && is_array($config['validation']['annotations']['namespaces'])) { - $container->setParameter('validator.annotations.namespaces', array_merge( - $container->getParameter('validator.annotations.namespaces'), - $config['validation']['annotations']['namespaces'] - )); - } + if (isset($config['annotations'])) { + // Register prefixes for constraint namespaces + if (!empty($config['annotations']['namespaces'])) { + $container->setParameter('validator.annotations.namespaces', array_merge( + $container->getParameter('validator.annotations.namespaces'), + $config['annotations']['namespaces'] + )); + } - $annotationLoader = new Definition($container->getParameter('validator.mapping.loader.annotation_loader.class')); - $annotationLoader->setPublic(false); - $annotationLoader->addArgument(new Parameter('validator.annotations.namespaces')); + // Register annotation loader + $annotationLoader = new Definition('%validator.mapping.loader.annotation_loader.class%'); + $annotationLoader->setPublic(false); + $annotationLoader->addArgument(new Parameter('validator.annotations.namespaces')); - $container->setDefinition('validator.mapping.loader.annotation_loader', $annotationLoader); + $container->setDefinition('validator.mapping.loader.annotation_loader', $annotationLoader); - $loader = $container->getDefinition('validator.mapping.loader.loader_chain'); - $arguments = $loader->getArguments(); - array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); - $loader->setArguments($arguments); - } - } elseif ($container->hasDefinition('validator')) { - $container->getDefinition('validator')->clearTags(); + $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); + $arguments = $loaderChain->getArguments(); + array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); + $loaderChain->setArguments($arguments); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index fc15e0804ff83..c7a5577ef7a2e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -80,20 +80,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new TranslatorPass()); $container->addCompilerPass(new AddCacheWarmerPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index a70f035342b6d..689185a4610c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -9,34 +9,50 @@ - - + + + - - + - + - + - + + + + + + + + + + + + + + + + + @@ -47,15 +63,9 @@ - - - - - - @@ -64,11 +74,14 @@ - - + + + + + @@ -83,21 +96,26 @@ - - + + - + - - + - + + + + + + - - + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php b/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php index 2c101de81610a..7721df5248599 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php @@ -6,19 +6,4 @@ class {{ bundle }} extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return strtr(__DIR__, '\\', '/'); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php new file mode 100644 index 0000000000000..20a272c4c78a2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -0,0 +1,45 @@ +loadFromExtension('app', 'config', array( + 'csrf_protection' => array( + 'enabled' => true, + 'field_name' => '_csrf', + 'secret' => 's3cr3t', + ), + 'esi' => array( + 'enabled' => true, + ), + 'profiler' => array( + 'only_exceptions' => true, + ), + 'router' => array( + 'resource' => '%kernel.root_dir%/config/routing.xml', + 'cache_warmer' => true, + ), + 'session' => array( + 'auto_start' => true, + 'class' => 'Session', + 'default_locale' => 'fr', + 'storage_id' => 'native', + 'name' => '_SYMFONY', + 'lifetime' => 86400, + 'path' => '/', + 'domain' => 'example.com', + 'secure' => true, + 'httponly' => true, + ), + 'templating' => array( + 'assets_version' => 'SomeVersionScheme', + 'assets_base_urls' => 'http://cdn.example.com', + 'cache_warmer' => true, + 'engines' => array('php', 'twig'), + 'loader' => array('loader.foo', 'loader.bar'), + ), + 'translator' => array( + 'enabled' => true, + 'fallback' => 'fr', + ), + 'validation' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php new file mode 100644 index 0000000000000..eb8deb9d1f01f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php @@ -0,0 +1,11 @@ +loadFromExtension('app', 'config', array( + 'session' => array( + 'storage_id' => 'pdo', + 'pdo.db_table' => 'table', + 'pdo.db_id_col' => 'id', + 'pdo.db_data_col' => 'data', + 'pdo.db_time_col' => 'time', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php new file mode 100644 index 0000000000000..bed6c991b44d2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php @@ -0,0 +1,12 @@ +loadFromExtension('app', 'config', array( + 'validation' => array( + 'enabled' => true, + 'annotations' => array( + 'namespaces' => array( + 'app' => 'Application\\Validator\\Constraints\\', + ), + ), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml new file mode 100644 index 0000000000000..ebc2f39fe6368 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + loader.foo + loader.bar + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml new file mode 100644 index 0000000000000..84455afff6d0c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml new file mode 100644 index 0000000000000..1af18e148f7f2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml new file mode 100644 index 0000000000000..bb8d3bc3bc90e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -0,0 +1,34 @@ +app.config: + csrf_protection: + enabled: true + field_name: _csrf + secret: s3cr3t + esi: + enabled: true + profiler: + only_exceptions: true + router: + resource: %kernel.root_dir%/config/routing.xml + cache_warmer: true + session: + auto_start: true + class: Session + default_locale: fr + storage_id: native + name: _SYMFONY + lifetime: 86400 + path: / + domain: example.com + secure: true + httponly: true + templating: + assets_version: SomeVersionScheme + assets_base_urls: http://cdn.example.com + cache_warmer: true + engines: [php, twig] + loader: [loader.foo, loader.bar] + translator: + enabled: true + fallback: fr + validation: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml new file mode 100644 index 0000000000000..06323d53de9b4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml @@ -0,0 +1,7 @@ +app.config: + session: + storage_id: pdo + pdo.db_table: table + pdo.db_id_col: id + pdo.db_data_col: data + pdo.db_time_col: time diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml new file mode 100644 index 0000000000000..1ff2545facb94 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml @@ -0,0 +1,6 @@ +app.config: + validation: + enabled: true + annotations: + namespaces: + app: Application\Validator\Constraints\ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 0e83eb1ff8169..99da686fa3969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -16,43 +16,182 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; -class FrameworkExtensionTest extends TestCase +abstract class FrameworkExtensionTest extends TestCase { - public function testConfigLoad() + abstract protected function loadFromFile(ContainerBuilder $container, $file); + + public function testCsrfProtection() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->getParameter('form.csrf_protection.enabled')); + $this->assertEquals('_csrf', $container->getParameter('form.csrf_protection.field_name')); + $this->assertEquals('s3cr3t', $container->getParameter('form.csrf_protection.secret')); + } + + public function testEsi() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('esi'), '->registerEsiConfiguration() loads esi.xml'); + } + + public function testProfiler() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('profiler'), '->registerProfilerConfiguration() loads profiling.xml'); + $this->assertTrue($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() loads collectors.xml'); + $this->assertTrue($container->getParameter('profiler_listener.only_exceptions')); + } + + public function testRouter() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('router.real'), '->registerRouterConfiguration() loads routing.xml'); + $this->assertEquals($container->getParameter('kernel.root_dir').'/config/routing.xml', $container->getParameter('routing.resource'), '->registerRouterConfiguration() sets routing resource'); + $this->assertTrue($container->getDefinition('router.cache_warmer')->hasTag('kernel.cache_warmer'), '->registerRouterConfiguration() tags router cache warmer if cache warming is set'); + $this->assertEquals('router.cached', (string) $container->getAlias('router'), '->registerRouterConfiguration() changes router alias to cached if cache warming is set'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRouterRequiresResourceOption() { - $container = $this->getContainer(); + $container = $this->createContainer(); $loader = new FrameworkExtension(); + $loader->configLoad(array(array('router' => true)), $container); + } + + public function testSession() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); + $this->assertEquals('fr', $container->getParameter('session.default_locale')); + $this->assertTrue($container->getDefinition('session')->hasMethodCall('start')); + $this->assertEquals('Session', $container->getParameter('session.class')); + $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); + + $options = $container->getParameter('session.storage.native.options'); + $this->assertEquals('_SYMFONY', $options['name']); + $this->assertEquals(86400, $options['lifetime']); + $this->assertEquals('/', $options['path']); + $this->assertEquals('example.com', $options['domain']); + $this->assertTrue($options['secure']); + $this->assertTrue($options['httponly']); + } + + public function testSessionPdo() + { + $container = $this->createContainerFromFile('session_pdo'); + $options = $container->getParameter('session.storage.pdo.options'); + + $this->assertEquals('session.storage.pdo', (string) $container->getAlias('session.storage')); + $this->assertEquals('table', $options['db_table']); + $this->assertEquals('id', $options['db_id_col']); + $this->assertEquals('data', $options['db_data_col']); + $this->assertEquals('time', $options['db_time_col']); + } + + public function testTemplating() + { + $container = $this->createContainerFromFile('full'); - $loader->configLoad(array(array()), $container); - $this->assertEquals('Symfony\\Bundle\\FrameworkBundle\\RequestListener', $container->getParameter('request_listener.class'), '->webLoad() loads the web.xml file if not already loaded'); + $this->assertTrue($container->hasDefinition('templating.name_parser'), '->registerTemplatingConfiguration() loads templating.xml'); + $this->assertEquals('SomeVersionScheme', $container->getParameter('templating.assets.version')); + $this->assertEquals('http://cdn.example.com', $container->getParameter('templating.assets.base_urls')); - $container = $this->getContainer(); + $this->assertTrue($container->getDefinition('templating.cache_warmer.template_paths')->hasTag('kernel.cache_warmer'), '->registerTemplatingConfiguration() tags templating cache warmer if cache warming is set'); + $this->assertEquals('templating.locator.cached', (string) $container->getAlias('templating.locator'), '->registerTemplatingConfiguration() changes templating.locator alias to cached if cache warming is set'); + + $this->assertEquals('templating.engine.delegating', (string) $container->getAlias('templating'), '->registerTemplatingConfiguration() configures delegating loader if multiple engines are provided'); + + $this->assertEquals('templating.loader.chain', (string) $container->getAlias('templating.loader'), '->registerTemplatingConfiguration() configures loader chain if multiple loaders are provided'); + } + + public function testTranslator() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('translator.real'), '->registerTranslatorConfiguration() loads translation.xml'); + $this->assertSame($container->getDefinition('translator.real'), $container->getDefinition('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator'); + + $this->assertContains( + realpath(__DIR__.'/../../Resources/translations/validators.fr.xliff'), + array_map(function($resource) { return $resource[1]; }, $container->getParameter('translation.resources')), + '->registerTranslatorConfiguration() finds FrameworkExtension translation resources' + ); + + $this->assertEquals('fr', $container->getParameter('translator.fallback_locale')); + } + + /** + * @expectedException LogicException + */ + public function testTemplatingRequiresAtLeastOneEngine() + { + $container = $this->createContainer(); $loader = new FrameworkExtension(); + $loader->configLoad(array(array('templating' => null)), $container); + } + + public function testValidation() + { + $container = $this->createContainerFromFile('full'); - // profiler - $loader->configLoad(array(array('profiler' => true)), $container); - $this->assertEquals('Symfony\Component\HttpKernel\Profiler\Profiler', $container->getParameter('profiler.class'), '->configLoad() loads the collectors.xml file if not already loaded'); + $this->assertTrue($container->hasDefinition('validator'), '->registerValidationConfiguration() loads validator.xml'); + $this->assertTrue($container->hasDefinition('validator.mapping.loader.xml_files_loader'), '->registerValidationConfiguration() defines the XML loader'); + $this->assertTrue($container->hasDefinition('validator.mapping.loader.yaml_files_loader'), '->registerValidationConfiguration() defines the YAML loader'); - // templating - $loader->configLoad(array(array('templating' => array('engines' => array('php')))), $container); - $this->assertEquals('Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', $container->getParameter('templating.engine.php.class'), '->templatingLoad() loads the templating.xml file if not already loaded'); + $xmlLoaderArgs = $container->getDefinition('validator.mapping.loader.xml_files_loader')->getArguments(); + $xmlFiles = $xmlLoaderArgs[0]; - // validation - $loader->configLoad(array(array('validation' => array('enabled' => true))), $container); - $this->assertEquals('Symfony\Component\Validator\Validator', $container->getParameter('validator.class'), '->validationLoad() loads the validation.xml file if not already loaded'); - $this->assertFalse($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->validationLoad() doesn\'t load the annotations service unless its needed'); + $this->assertContains( + realpath(__DIR__.'/../../../../Component/Form/Resources/config/validation.xml'), + array_map('realpath', $xmlFiles), + '->registerValidationConfiguration() adds Form validation.xml to XML loader' + ); - $loader->configLoad(array(array('validation' => array('enabled' => true, 'annotations' => true))), $container); - $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->validationLoad() loads the annotations service'); + $this->assertFalse($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() does not define the annotation loader unless needed'); } - protected function getContainer() + public function testValidationAnnotations() + { + $container = $this->createContainerFromFile('validation_annotations'); + + $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() defines the annotation loader'); + + $namespaces = $container->getParameter('validator.annotations.namespaces'); + $this->assertEquals('Symfony\\Component\\Validator\\Constraints\\', $namespaces['validation'], '->registerValidationConfiguration() loads the default "validation" namespace'); + $this->assertEquals('Application\\Validator\\Constraints\\', $namespaces['app'], '->registerValidationConfiguration() loads custom validation namespaces'); + } + + protected function createContainer() { return new ContainerBuilder(new ParameterBag(array( 'kernel.bundles' => array('FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'), - 'kernel.root_dir' => __DIR__, - 'kernel.debug' => false, + 'kernel.cache_dir' => __DIR__, 'kernel.compiled_classes' => array(), + 'kernel.debug' => false, + 'kernel.environment' => 'test', + 'kernel.name' => 'kernel', + 'kernel.root_dir' => __DIR__, ))); } + + protected function createContainerFromFile($file) + { + $container = $this->createContainer(); + $container->registerExtension(new FrameworkExtension()); + $this->loadFromFile($container, $file); + + $container->getCompilerPassConfig()->setOptimizationPasses(array()); + $container->getCompilerPassConfig()->setRemovingPasses(array()); + $container->compile(); + + return $container; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php new file mode 100644 index 0000000000000..451d16364a329 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; + +class PhpFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new PhpFileLoader($container, __DIR__.'/Fixtures/php'); + $loader->load($file.'.php'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php new file mode 100644 index 0000000000000..ea6fbe08a7335 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; + +class XmlFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new XmlFileLoader($container, __DIR__.'/Fixtures/xml'); + $loader->load($file.'.xml'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php new file mode 100644 index 0000000000000..c1af39109e980 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +class YamlFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new YamlFileLoader($container, __DIR__.'/Fixtures/yml'); + $loader->load($file.'.yml'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php index 1342b9b5eed32..22b95430711fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php @@ -27,20 +27,4 @@ public function getParent() { return 'SensioFooBundle'; } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php index 98dd4342273a2..af57d44bcdd2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php @@ -20,19 +20,4 @@ */ class FooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php index 811ed2f14115a..9e6918d34e955 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php @@ -20,19 +20,4 @@ */ class SensioCmsFooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php index 2b547f923ece1..e8930625f3b19 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php @@ -20,19 +20,4 @@ */ class SensioFooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..5f48157cb313e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php @@ -0,0 +1,234 @@ + + */ +class Configuration +{ + public function getAclConfigTree() + { + $tb = new TreeBuilder(); + + return $tb + ->root('security:acl', 'array') + ->scalarNode('connection')->end() + ->scalarNode('cache')->end() + ->end() + ->buildTree(); + } + + public function getFactoryConfigTree() + { + $tb = new TreeBuilder(); + + return $tb + ->root('security:config', 'array') + ->fixXmlConfig('factory', 'factories') + ->arrayNode('factories') + ->prototype('scalar')->end() + ->end() + ->end() + ->buildTree(); + } + + public function getMainConfigTree(array $factories) + { + $tb = new TreeBuilder(); + $rootNode = $tb->root('security:config', 'array'); + + $rootNode + ->scalarNode('access_denied_url')->end() + ->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end() + ; + + $this->addEncodersSection($rootNode); + $this->addProvidersSection($rootNode); + $this->addFirewallsSection($rootNode, $factories); + $this->addAccessControlSection($rootNode); + $this->addRoleHierarchySection($rootNode); + + return $tb->buildTree(); + } + + protected function addRoleHierarchySection($rootNode) + { + $rootNode + ->fixXmlConfig('role', 'role_hierarchy') + ->arrayNode('role_hierarchy') + ->containsNameValuePairsWithKeyAttribute('id') + ->prototype('array') + ->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end() + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['value']); }) + ->then(function($v) { return preg_split('/\s*,\s*/', $v['value']); }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ; + } + + protected function addAccessControlSection($rootNode) + { + $rootNode + ->fixXmlConfig('rule', 'access_control') + ->arrayNode('access_control') + ->cannotBeOverwritten() + ->prototype('array') + ->scalarNode('requires_channel')->defaultNull()->end() + ->scalarNode('path')->defaultNull()->end() + ->scalarNode('host')->defaultNull()->end() + ->scalarNode('ip')->defaultNull()->end() + ->arrayNode('methods') + ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->prototype('scalar')->end() + ->end() + ->fixXmlConfig('role') + ->arrayNode('roles') + ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->prototype('scalar')->end() + ->end() + ->fixXmlConfig('attribute') + ->arrayNode('attributes') + ->containsNameValuePairsWithKeyAttribute('key') + ->prototype('scalar') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['pattern']); }) + ->then(function($v) { return $v['pattern']; }) + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + protected function addFirewallsSection($rootNode, array $factories) + { + $firewallNodeBuilder = + $rootNode + ->fixXmlConfig('firewall') + ->arrayNode('firewalls') + ->disallowNewKeysInSubsequentConfigs() + ->useAttributeAsKey('name') + ->prototype('array') + ->scalarNode('pattern')->end() + ->booleanNode('security')->defaultTrue()->end() + ->scalarNode('request_matcher')->end() + ->scalarNode('access_denied_url')->end() + ->scalarNode('access_denied_handler')->end() + ->scalarNode('entry_point')->end() + ->scalarNode('provider')->end() + ->booleanNode('stateless')->defaultFalse()->end() + ->scalarNode('context')->cannotBeEmpty()->end() + ->arrayNode('logout') + ->treatTrueLike(array()) + ->canBeUnset() + ->scalarNode('path')->defaultValue('/logout')->end() + ->scalarNode('target')->defaultValue('/')->end() + ->booleanNode('invalidate_session')->defaultTrue()->end() + ->fixXmlConfig('delete_cookie') + ->arrayNode('delete_cookies') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && is_int(key($v)); }) + ->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); }) + ->end() + ->useAttributeAsKey('name') + ->prototype('array') + ->scalarNode('path')->defaultNull()->end() + ->scalarNode('domain')->defaultNull()->end() + ->end() + ->end() + ->fixXmlConfig('handler') + ->arrayNode('handlers') + ->prototype('scalar')->end() + ->end() + ->end() + ->booleanNode('anonymous')->end() + ->arrayNode('switch_user') + ->scalarNode('provider')->end() + ->scalarNode('parameter')->defaultValue('_switch_user')->end() + ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end() + ->end() + ; + + foreach ($factories as $factoriesAtPosition) { + foreach ($factoriesAtPosition as $factory) { + $factoryNode = + $firewallNodeBuilder->arrayNode(str_replace('-', '_', $factory->getKey())) + ->canBeUnset() + ; + + $factory->addConfiguration($factoryNode); + } + } + } + + protected function addProvidersSection($rootNode) + { + $rootNode + ->fixXmlConfig('provider') + ->arrayNode('providers') + ->disallowNewKeysInSubsequentConfigs() + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ->scalarNode('id')->end() + ->fixXmlConfig('provider') + ->arrayNode('providers') + ->prototype('scalar')->end() + ->end() + ->fixXmlConfig('user') + ->arrayNode('users') + ->useAttributeAsKey('name') + ->prototype('array') + ->scalarNode('password')->defaultValue(uniqid())->end() + ->arrayNode('roles') + ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('entity') + ->scalarNode('class')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('property')->defaultNull()->end() + ->end() + ->arrayNode('document') + ->scalarNode('class')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('property')->defaultNull()->end() + ->end() + ->end() + ->end() + ; + } + + protected function addEncodersSection($rootNode) + { + $rootNode + ->fixXmlConfig('encoder') + ->arrayNode('encoders') + ->useAttributeAsKey('class') + ->prototype('array') + ->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end() + ->scalarNode('algorithm')->isRequired()->cannotBeEmpty()->end() + ->booleanNode('ignore_case')->end() + ->booleanNode('encode_as_base64')->end() + ->scalarNode('iterations')->end() + ->scalarNode('id')->end() + ->end() + ->end() + ; + } +} \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index 860ba9e145b99..88b481e3b1f16 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; + use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -38,10 +40,6 @@ abstract class AbstractFactory implements SecurityFactoryInterface public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId) { - if (!is_array($config)) { - $config = array(); - } - // authentication provider $authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId); $container @@ -66,6 +64,24 @@ public function create(ContainerBuilder $container, $id, $config, $userProviderI return array($authProviderId, $listenerId, $entryPointId); } + public function addConfiguration(NodeBuilder $node) + { + $node + ->scalarNode('provider')->end() + ->booleanNode('remember_me')->defaultTrue()->end() + ->scalarNode('success_handler')->end() + ->scalarNode('failure_handler')->end() + ; + + foreach ($this->options as $name => $default) { + if (is_bool($default)) { + $node->booleanNode($name)->defaultValue($default); + } else { + $node->scalarNode($name)->defaultValue($default); + } + } + } + public final function addOption($name, $default = null) { $this->options[$name] = $default; @@ -127,18 +143,15 @@ protected function createEntryPoint($container, $id, $config, $defaultEntryPoint */ protected function isRememberMeAware($config) { - return !isset($config['remember_me']) || (Boolean) $config['remember_me']; + return $config['remember_me']; } protected function createListener($container, $id, $config, $userProvider) { - // merge set options with default options - $options = $this->getOptionsFromConfig($config); - $listenerId = $this->getListenerId(); $listener = new DefinitionDecorator($listenerId); $listener->setArgument(3, $id); - $listener->setArgument(4, $options); + $listener->setArgument(4, array_intersect_key($config, $this->options)); // success handler if (isset($config['success_handler'])) { @@ -155,17 +168,4 @@ protected function createListener($container, $id, $config, $userProvider) return $listenerId; } - - protected final function getOptionsFromConfig($config) - { - $options = $this->options; - - foreach (array_keys($options) as $key) { - if (array_key_exists($key, $config)) { - $options[$key] = $config[$key]; - } - } - - return $options; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index 6149361fb8871..eef9aecdc4251 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -59,14 +59,11 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config, protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) { - // merge set options with default options - $options = $this->getOptionsFromConfig($config); - $entryPointId = 'security.authentication.form_entry_point.'.$id; $container ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point')) - ->addArgument($options['login_path']) - ->addArgument($options['use_forward']) + ->addArgument($config['login_path']) + ->addArgument($config['use_forward']) ; return $entryPointId; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 3686a1e06f1d2..9b2b7a870a864 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; + use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -53,4 +55,11 @@ public function getKey() { return 'http-basic'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php index d837032e41096..49cf748cf40e9 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; + use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -53,4 +55,11 @@ public function getKey() { return 'http-digest'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index e8646e90d09a6..c4727429ab5e3 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -2,6 +2,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; + use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; @@ -10,16 +12,19 @@ class RememberMeFactory implements SecurityFactoryInterface { + protected $options = array( + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ); + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { - if (!isset($config['key']) || empty($config['key'])) { - throw new \RuntimeException('A "key" must be defined for each remember-me section.'); - } - - if (isset($config['provider'])) { - throw new \RuntimeException('You must not set a user provider for remember-me.'); - } - // authentication provider $authProviderId = 'security.authentication.provider.rememberme.'.$id; $container @@ -60,22 +65,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, } // remember-me options - $options = array( - 'name' => 'REMEMBERME', - 'lifetime' => 31536000, - 'path' => '/', - 'domain' => null, - 'secure' => false, - 'httponly' => true, - 'always_remember_me' => false, - 'remember_me_parameter' => '_remember_me', - ); - foreach ($options as $name => $option) { - if (array_key_exists($name, $config)) { - $options[$name] = $config[$name]; - } - } - $rememberMeServices->setArgument(3, $options); + $rememberMeServices->setArgument(3, array_intersect_key($config, $this->options)); // attach to remember-me aware listeners $userProviders = array(); @@ -118,4 +108,20 @@ public function getKey() { return 'remember-me'; } + + public function addConfiguration(NodeBuilder $node) + { + $node + ->scalarNode('key')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('token_provider')->end() + ; + + foreach ($this->options as $name => $value) { + if (is_bool($value)) { + $node->booleanNode($name)->defaultValue($value); + } else { + $node->scalarNode($name)->defaultValue($value); + } + } + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php index a2ec07df051ce..05dcc74f8a4c8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder; /** @@ -25,4 +26,6 @@ function create(ContainerBuilder $container, $id, $config, $userProvider, $defau function getPosition(); function getKey(); + + function addConfiguration(NodeBuilder $builder); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index dbf0e359148ac..d53f75d978ca8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder; + use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -50,4 +52,11 @@ public function getKey() { return 'x509'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index edc27e69f21da..ac835082bd1a8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; +use Symfony\Component\DependencyInjection\Configuration\Processor; +use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -34,44 +36,38 @@ class SecurityExtension extends Extension protected $requestMatchers = array(); protected $contextListeners = array(); protected $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me'); + protected $configuration; + protected $factories; - public function configLoad(array $configs, ContainerBuilder $container) + public function __construct() { - foreach ($configs as $config) { - $this->doConfigLoad($this->normalizeKeys($config), $container); - } + $this->configuration = new Configuration(); } - public function aclLoad(array $configs, ContainerBuilder $container) + public function configLoad(array $configs, ContainerBuilder $container) { - foreach ($configs as $config) { - $this->doAclLoad($this->normalizeKeys($config), $container); - } - } + $processor = new Processor(); - /** - * Loads the web configuration. - * - * @param array $config An array of configuration settings - * @param ContainerBuilder $container A ContainerBuilder instance - */ - protected function doConfigLoad($config, ContainerBuilder $container) - { - if (!$container->hasDefinition('security.context')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('security.xml'); - $loader->load('security_listeners.xml'); - $loader->load('security_rememberme.xml'); - $loader->load('templating_php.xml'); - $loader->load('templating_twig.xml'); - $loader->load('collectors.xml'); - } + // first assemble the factories + $factories = $this->createListenerFactories($container, $processor->process($this->configuration->getFactoryConfigTree(), $configs)); + + // normalize and merge the actual configuration + $tree = $this->configuration->getMainConfigTree($factories); + $config = $processor->process($tree, $configs); + // load services + $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $loader->load('security.xml'); + $loader->load('security_listeners.xml'); + $loader->load('security_rememberme.xml'); + $loader->load('templating_php.xml'); + $loader->load('templating_twig.xml'); + $loader->load('collectors.xml'); + + // set some global scalars if (isset($config['access_denied_url'])) { $container->setParameter('security.access.denied_url', $config['access_denied_url']); } - - // session fixation protection if (isset($config['session_fixation_protection'])) { $container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_protection']); } @@ -79,97 +75,95 @@ protected function doConfigLoad($config, ContainerBuilder $container) $this->createFirewalls($config, $container); $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); - - return $container; } - protected function createRoleHierarchy($config, ContainerBuilder $container) + public function aclLoad(array $configs, ContainerBuilder $container) { - $roles = array(); - if (isset($config['role_hierarchy'])) { - $roles = $config['role_hierarchy']; + $processor = new Processor(); + $config = $processor->process($this->configuration->getAclConfigTree(), $configs); + + $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $loader->load('security_acl.xml'); + + if (isset($config['connection'])) { + $container->setAlias('security.acl.dbal.connection', sprintf('doctrine.dbal.%s_connection', $config['connection'])); } - if (isset($roles['role']) && is_int(key($roles['role']))) { - $roles = $roles['role']; + if (isset($config['cache'])) { + $container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache'])); } + } - $hierarchy = array(); - foreach ($roles as $id => $role) { - if (is_array($role) && isset($role['id'])) { - $id = $role['id']; - } + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/schema'; + } - $value = $role; - if (is_array($role) && isset($role['value'])) { - $value = $role['value']; - } + public function getNamespace() + { + return 'http://www.symfony-project.org/schema/dic/security'; + } + + public function getAlias() + { + return 'security'; + } + + /** + * Loads the web configuration. + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ - $hierarchy[$id] = is_array($value) ? $value : preg_split('/\s*,\s*/', $value); + protected function createRoleHierarchy($config, ContainerBuilder $container) + { + if (!isset($config['role_hierarchy'])) { + return; } - $container->setParameter('security.role_hierarchy.roles', $hierarchy); + $container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']); $container->remove('security.access.simple_role_voter'); $container->getDefinition('security.access.role_hierarchy_voter')->addTag('security.voter'); } protected function createAuthorization($config, ContainerBuilder $container) { - $rules = array(); - if (isset($config['access_control'])) { - $rules = $config['access_control']; - } - - if (isset($rules['rule']) && is_array($rules['rule'])) { - $rules = $rules['rule']; + if (!isset($config['access_control'])) { + return; } - foreach ($rules as $i => $access) { - $roles = isset($access['role']) ? (is_array($access['role']) ? $access['role'] : preg_split('/\s*,\s*/', $access['role'])) : array(); - $channel = null; - if (isset($access['requires_channel'])) { - $channel = $access['requires_channel']; - } - - // matcher - $path = $host = $methods = $ip = null; - if (isset($access['path'])) { - $path = $access['path']; - } - if (isset($access['host'])) { - $host = $access['host']; - } - if (count($tMethods = $this->normalizeConfig($access, 'method')) > 0) { - $methods = $tMethods; - } - if (isset($access['ip'])) { - $ip = $access['ip']; - } - - $matchAttributes = array(); - $attributes = $this->normalizeConfig($access, 'attribute'); - foreach ($attributes as $key => $attribute) { - if (isset($attribute['key'])) { - $key = $attribute['key']; - } - $matchAttributes[$key] = $attribute['pattern']; - } - $matcher = $this->createRequestMatcher($container, $path, $host, $methods, $ip, $matchAttributes); + foreach ($config['access_control'] as $access) { + $matcher = $this->createRequestMatcher( + $container, + $access['path'], + $access['host'], + count($access['methods']) === 0 ? null : $access['methods'], + $access['ip'], + $access['attributes'] + ); - $container->getDefinition('security.access_map')->addMethodCall('add', array($matcher, $roles, $channel)); + $container->getDefinition('security.access_map') + ->addMethodCall('add', array($matcher, $access['roles'], $access['requires_channel'])); } } protected function createFirewalls($config, ContainerBuilder $container) { + if (!isset($config['firewalls'])) { + return; + } + + $firewalls = $config['firewalls']; $providerIds = $this->createUserProviders($config, $container); $this->createEncoders($config, $container); - if (!$firewalls = $this->normalizeConfig($config, 'firewall')) { - return; - } - // make the ContextListener aware of the configured user providers $definition = $container->getDefinition('security.context_listener'); $arguments = $definition->getArguments(); @@ -185,16 +179,8 @@ protected function createFirewalls($config, ContainerBuilder $container) // load firewall map $mapDef = $container->getDefinition('security.firewall.map'); - $names = $map = array(); + $map = array(); foreach ($firewalls as $name => $firewall) { - if (isset($firewall['name'])) { - $name = $firewall['name']; - } - if (in_array($name, $names)) { - throw new \RuntimeException(sprintf('The firewall name must be unique. Duplicate found: "%s"', $name)); - } - $names[] = $name; - list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $providerIds, $factories); $contextId = 'security.firewall.map.context.'.$name; @@ -220,7 +206,7 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ } // Security disabled? - if (isset($firewall['security']) && !$firewall['security']) { + if (false === $firewall['security']) { return array($matcher, array(), null); } @@ -228,9 +214,6 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ if (isset($firewall['provider'])) { $defaultProvider = $this->getUserProviderId($firewall['provider']); } else { - if (!$providerIds) { - throw new \InvalidArgumentException('You must provide at least one authentication provider.'); - } $defaultProvider = reset($providerIds); } @@ -242,7 +225,7 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ $listeners[] = new Reference('security.channel_listener'); // Context serializer listener - if (!isset($firewall['stateless']) || !$firewall['stateless']) { + if (false === $firewall['stateless']) { $contextKey = $id; if (isset($firewall['context'])) { $contextKey = $firewall['context']; @@ -252,44 +235,29 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ } // Logout listener - if (array_key_exists('logout', $firewall)) { + if (isset($firewall['logout'])) { $listenerId = 'security.logout_listener.'.$id; $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener')); - + $listener->addArgument($firewall['logout']['path']); + $listener->addArgument($firewall['logout']['target']); $listeners[] = new Reference($listenerId); - if (!is_array($firewall['logout'])) { - $firewall['logout'] = array(); - } - - if (isset($firewall['logout']['path'])) { - $listener->setArgument(1, $firewall['logout']['path']); - } - - if (isset($firewall['logout']['target'])) { - $listener->setArgument(2, $firewall['logout']['target']); - } - // add session logout handler - $invalidateSession = true; - if (isset($firewall['logout']['invalidate_session'])) { - $invalidateSession = (Boolean) $firewall['logout']['invalidate_session']; - } - if (true === $invalidateSession && (!isset($firewall['stateless']) || !$firewall['stateless'])) { + if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) { $listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session'))); } // add cookie logout handler - if (count($cookies = $this->normalizeConfig($firewall['logout'], 'cookie')) > 0) { + if (count($firewall['logout']['delete_cookies']) > 0) { $cookieHandlerId = 'security.logout.handler.cookie_clearing.'.$id; $cookieHandler = $container->setDefinition($cookieHandlerId, new DefinitionDecorator('security.logout.handler.cookie_clearing')); - $cookieHandler->addArgument($cookies); + $cookieHandler->addArgument($firewall['logout']['delete_cookies']); $listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId))); } // add custom handlers - foreach ($this->normalizeConfig($firewall['logout'], 'handler') as $handlerId) { + foreach ($firewall['logout']['handlers'] as $handlerId) { $listener->addMethodCall('addHandler', array(new Reference($handlerId))); } } @@ -303,7 +271,7 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ $listeners[] = new Reference('security.access_listener'); // Switch user listener - if (array_key_exists('switch_user', $firewall)) { + if (isset($firewall['switch_user'])) { $listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider)); } @@ -340,13 +308,9 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de foreach ($this->listenerPositions as $position) { foreach ($factories[$position] as $factory) { - $key = $factory->getKey(); - $keybis = str_replace('-', '_', $key); + $key = str_replace('-', '_', $factory->getKey()); - if (array_key_exists($keybis, $firewall)) { - $firewall[$key] = $firewall[$keybis]; - } - if (array_key_exists($key, $firewall) && $firewall[$key] !== false) { + if (isset($firewall[$key])) { $userProvider = isset($firewall[$key]['provider']) ? $this->getUserProviderId($firewall[$key]['provider']) : $defaultProvider; list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint); @@ -359,7 +323,7 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de } // Anonymous - if (array_key_exists('anonymous', $firewall)) { + if (isset($firewall['anonymous'])) { $listeners[] = new Reference('security.authentication.listener.anonymous'); $hasListeners = true; } @@ -371,66 +335,14 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de return array($listeners, $providers, $defaultEntryPoint); } - // Parses user providers and returns an array of their ids - protected function createUserProviders($config, ContainerBuilder $container) - { - $providers = $this->normalizeConfig($config, 'provider'); - if (!$providers) { - return array(); - } - - $providerIds = array(); - foreach ($providers as $name => $provider) { - $id = $this->createUserDaoProvider($name, $provider, $container); - - if (in_array($id, $providerIds, true)) { - throw new \RuntimeException(sprintf('Provider names must be unique. Duplicate entry for %s.', $id)); - } - - $providerIds[] = $id; - } - - return $providerIds; - } - - protected function createListenerFactories(ContainerBuilder $container, $config) - { - // load service templates - $c = new ContainerBuilder(); - $parameterBag = $container->getParameterBag(); - $loader = new XmlFileLoader($c, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('security_factories.xml'); - - // load user-created listener factories - foreach ($this->normalizeConfig($config, 'factory', 'factories') as $factory) { - $loader->load($parameterBag->resolveValue($factory)); - } - - $tags = $c->findTaggedServiceIds('security.listener.factory'); - - $factories = array(); - foreach ($this->listenerPositions as $position) { - $factories[$position] = array(); - } - - foreach (array_keys($tags) as $tag) { - $factory = $c->get($tag); - - $factories[$factory->getPosition()][] = $factory; - } - - return $factories; - } - protected function createEncoders($config, ContainerBuilder $container) { - $encoders = $this->normalizeConfig($config, 'encoder'); - if (!$encoders) { - return array(); + if (!isset($config['encoders'])) { + return; } $encoderMap = array(); - foreach ($encoders as $class => $encoder) { + foreach ($config['encoders'] as $class => $encoder) { $encoderMap = $this->createEncoder($encoderMap, $class, $encoder, $container); } @@ -442,21 +354,6 @@ protected function createEncoders($config, ContainerBuilder $container) protected function createEncoder(array $encoderMap, $accountClass, $config, ContainerBuilder $container) { - if (is_array($config) && isset($config['class'])) { - $accountClass = $config['class']; - } - - if (empty($accountClass)) { - throw new \RuntimeException('Each encoder needs an account class.'); - } - - // a minimal message digest, or plaintext encoder - if (is_string($config)) { - $config = array( - 'algorithm' => $config, - ); - } - // a custom encoder service if (isset($config['id'])) { $container @@ -467,17 +364,12 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont return $encoderMap; } - // a lazy loaded, message digest or plaintext encoder - if (!isset($config['algorithm'])) { - throw new \RuntimeException('"algorithm" must be defined.'); - } - // plaintext encoder if ('plaintext' === $config['algorithm']) { $arguments = array(); if (isset($config['ignore_case'])) { - $arguments[0] = (Boolean) $config['ignore_case']; + $arguments[0] = $config['ignore_case']; } $encoderMap[$accountClass] = array( @@ -493,7 +385,7 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont // add optional arguments if (isset($config['encode_as_base64'])) { - $arguments[1] = (Boolean) $config['encode_as_base64']; + $arguments[1] = $config['encode_as_base64']; } else { $arguments[1] = false; } @@ -512,17 +404,23 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont return $encoderMap; } - // Parses a tag and returns the id for the related user provider service - protected function createUserDaoProvider($name, $provider, ContainerBuilder $container, $master = true) + // Parses user providers and returns an array of their ids + protected function createUserProviders($config, ContainerBuilder $container) { - if (isset($provider['name'])) { - $name = $provider['name']; + $providerIds = array(); + foreach ($config['providers'] as $name => $provider) { + $id = $this->createUserDaoProvider($name, $provider, $container); + $providerIds[] = $id; } - if (!$name) { - throw new \RuntimeException('You must define a name for each user provider.'); - } + return $providerIds; + } + // Parses a tag and returns the id for the related user provider service + // FIXME: Replace register() calls in this method with DefinitionDecorator + // and move the actual definition to an xml file + protected function createUserDaoProvider($name, $provider, ContainerBuilder $container, $master = true) + { $name = $this->getUserProviderId(strtolower($name)); // Existing DAO service provider @@ -533,7 +431,7 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con } // Chain provider - if (isset($provider['provider'])) { + if (count($provider['providers']) > 0) { // FIXME throw new \RuntimeException('Not implemented yet.'); } @@ -546,8 +444,9 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con ->setArguments(array( new Reference('security.user.entity_manager'), $provider['entity']['class'], - isset($provider['entity']['property']) ? $provider['entity']['property'] : null, - )); + $provider['entity']['property'], + )) + ; return $name; } @@ -560,7 +459,7 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con ->setArguments(array( new Reference('security.user.document_manager'), $provider['document']['class'], - isset($provider['document']['property']) ? $provider['document']['property'] : null, + $provider['document']['property'], )); return $name; @@ -569,27 +468,8 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con // In-memory DAO provider $definition = $container->register($name, '%security.user.provider.in_memory.class%'); $definition->setPublic(false); - foreach ($this->normalizeConfig($provider, 'user') as $username => $user) { - if (isset($user['name'])) { - $username = $user['name']; - } - - if (!array_key_exists('password', $user)) { - // if no password is provided explicitly, it means that - // the user will be used with OpenID, X.509 certificates, ... - // Let's generate a random password just to be sure this - // won't be used accidentally with other authentication schemes. - // If you want an empty password, just say so explicitly - $user['password'] = uniqid(); - } - - if (!isset($user['roles'])) { - $user['roles'] = array(); - } else { - $user['roles'] = is_array($user['roles']) ? $user['roles'] : preg_split('/\s*,\s*/', $user['roles']); - } - - $userId = $name.'_'.md5(serialize(array($username, $user['password'], $user['roles']))); + foreach ($provider['users'] as $username => $user) { + $userId = $name.'_'.md5(json_encode(array($username, $user['password'], $user['roles']))); $container ->register($userId, 'Symfony\Component\Security\Core\User\User') @@ -632,14 +512,8 @@ protected function createSwitchUserListener($container, $id, $config, $defaultPr $listener = $container->setDefinition($switchUserListenerId, new DefinitionDecorator('security.authentication.switchuser_listener')); $listener->setArgument(1, new Reference($userProvider)); $listener->setArgument(3, $id); - - if (isset($config['parameter'])) { - $listener->setArgument(5, $config['parameter']); - } - - if (isset($config['role'])) { - $listener->setArgument(6, $config['role']); - } + $listener->addArgument($config['parameter']); + $listener->addArgument($config['role']); return $switchUserListenerId; } @@ -668,42 +542,35 @@ protected function createRequestMatcher($container, $path = null, $host = null, return $this->requestMatchers[$id] = new Reference($id); } - protected function doAclLoad(array $config, ContainerBuilder $container) + protected function createListenerFactories(ContainerBuilder $container, $config) { - if (!$container->hasDefinition('security.acl')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('security_acl.xml'); + if (null !== $this->factories) { + return $this->factories; } - if (isset($config['connection'])) { - $container->setAlias('security.acl.dbal.connection', sprintf('doctrine.dbal.%s_connection', $config['connection'])); - } + // load service templates + $c = new ContainerBuilder(); + $parameterBag = $container->getParameterBag(); + $loader = new XmlFileLoader($c, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $loader->load('security_factories.xml'); - if (isset($config['cache'])) { - $container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache'])); - } else { - $container->remove('security.acl.cache.doctrine'); - $container->removeAlias('security.acl.cache.doctrine.cache_impl'); + // load user-created listener factories + foreach ($config['factories'] as $factory) { + $loader->load($parameterBag->resolveValue($factory)); } - } - /** - * Returns the base path for the XSD files. - * - * @return string The XSD base path - */ - public function getXsdValidationBasePath() - { - return __DIR__.'/../Resources/config/schema'; - } + $tags = $c->findTaggedServiceIds('security.listener.factory'); - public function getNamespace() - { - return 'http://www.symfony-project.org/schema/dic/security'; - } + $factories = array(); + foreach ($this->listenerPositions as $position) { + $factories[$position] = array(); + } - public function getAlias() - { - return 'security'; + foreach (array_keys($tags) as $tag) { + $factory = $c->get($tag); + $factories[$factory->getPosition()][] = $factory; + } + + return $this->factories = $factories; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 3245168813088..f36a6c461a7ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -28,12 +28,8 @@ Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener Symfony\Component\Security\Http\Firewall\SwitchUserListener - ROLE_ALLOWED_TO_SWITCH - _switch_user Symfony\Component\Security\Http\Firewall\LogoutListener - /logout - / Symfony\Component\Security\Http\Logout\SessionLogoutHandler Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler @@ -83,14 +79,12 @@ - + - %security.logout.path% - %security.logout.target_path% @@ -162,13 +156,11 @@ - + - %security.authentication.switchuser.parameter% - %security.authentication.switchuser.role% diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 46436fb27d91c..b62d9b7557077 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -30,20 +30,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddAuthenticationProvidersPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php new file mode 100644 index 0000000000000..988640fb284dc --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php @@ -0,0 +1,15 @@ +load('merge_import.php', $container); + +$container->loadFromExtension('security', 'config', array( + 'firewalls' => array( + 'main' => array( + 'form_login' => false, + 'http_basic' => null, + ), + ), + 'role_hierarchy' => array( + 'FOO' => array('MOO'), + ) +)); \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php new file mode 100644 index 0000000000000..2b9be399d77c7 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php @@ -0,0 +1,15 @@ +loadFromExtension('security', 'config', array( + 'firewalls' => array( + 'main' => array( + 'form_login' => array( + 'login_path' => '/login', + ) + ) + ), + 'role_hierarchy' => array( + 'FOO' => 'BAR', + 'ADMIN' => 'USER', + ), +)); \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml index c01acd61013eb..2ca9f5b9d0c7d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml @@ -6,12 +6,9 @@ xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd"> - - - - - - - + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml index 795105230cd04..4c8985a79157a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml @@ -6,10 +6,8 @@ xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd"> - - ROLE_USER - ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH - ROLE_USER,ROLE_ADMIN - + ROLE_USER + ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH + ROLE_USER,ROLE_ADMIN diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml new file mode 100644 index 0000000000000..36f7b4de7208f --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml new file mode 100644 index 0000000000000..806719ab5fa07 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml new file mode 100644 index 0000000000000..a42fc99fab00c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml @@ -0,0 +1,11 @@ +imports: + - { resource: merge_import.yml } + +security.config: + firewalls: + main: + form_login: false + http_basic: ~ + + role_hierarchy: + FOO: [MOO] \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml new file mode 100644 index 0000000000000..497fb398c770c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml @@ -0,0 +1,9 @@ +security.config: + firewalls: + main: + form_login: + login_path: /login + + role_hierarchy: + FOO: BAR + ADMIN: USER diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php index 9d6d1390ee062..c412ce0e30d00 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php @@ -28,7 +28,7 @@ public function testCreate() list($authProviderId, $listenerId, $entryPointId - ) = $factory->create($container, 'foo', array('use_forward' => true, 'failure_path' => '/foo', 'success_handler' => 'foo'), 'user_provider', 'entry_point'); + ) = $factory->create($container, 'foo', array('use_forward' => true, 'failure_path' => '/foo', 'success_handler' => 'foo', 'remember_me' => true), 'user_provider', 'entry_point'); // auth provider $this->assertEquals('auth_provider', $authProviderId); @@ -41,15 +41,8 @@ public function testCreate() $this->assertEquals(array( 'index_3' => 'foo', 'index_4' => array( - 'check_path' => '/login_check', - 'login_path' => '/login', 'use_forward' => true, - 'always_use_default_target_path' => false, - 'default_target_path' => '/', - 'target_path_parameter' => '_target_path', - 'use_referer' => false, 'failure_path' => '/foo', - 'failure_forward' => false, ), 'index_5' => new Reference('foo'), ), $definition->getArguments()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index d76f505d93952..73df0096914ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -36,10 +36,10 @@ public function testUserProviders() $expectedProviders = array( 'security.authentication.provider.digest', - 'security.authentication.provider.digest_0ff1b54f2a4b7f71b2b9d6604fcca4b8', + 'security.authentication.provider.digest_23374fce51fe846516ff85bfa9add8fe', 'security.authentication.provider.basic', - 'security.authentication.provider.basic_b7f0cf21802ffc8b22cadbb255f07213', - 'security.authentication.provider.basic_98e44377704554700e68c22094b51ca4', + 'security.authentication.provider.basic_745e8583f784c83c4b4208fd281001f3', + 'security.authentication.provider.basic_af4bcce7246fb064b8e219034043d88a', 'security.authentication.provider.doctrine', 'security.authentication.provider.service', 'security.authentication.provider.anonymous', @@ -109,6 +109,16 @@ public function testAccess() } } + public function testMerge() + { + $container = $this->getContainer('merge'); + + $this->assertEquals(array( + 'FOO' => array('MOO'), + 'ADMIN' => array('USER'), + ), $container->getParameter('security.role_hierarchy.roles')); + } + protected function getContainer($file) { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php b/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php index 7480863e96580..8b0a3a4a10148 100644 --- a/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php +++ b/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php @@ -20,19 +20,4 @@ */ class SwiftmailerBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index dff10138a6da3..d27d85bcd2d67 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -28,20 +28,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new TwigEnvironmentPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php index 0a265a0004f7a..b3558f8ab3da0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php +++ b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php @@ -20,19 +20,4 @@ */ class WebProfilerBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/ZendBundle/ZendBundle.php b/src/Symfony/Bundle/ZendBundle/ZendBundle.php index e53ad48cb6476..c06f2636ab746 100644 --- a/src/Symfony/Bundle/ZendBundle/ZendBundle.php +++ b/src/Symfony/Bundle/ZendBundle/ZendBundle.php @@ -28,20 +28,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new ZendLoggerWriterPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Component/CssSelector/Parser.php b/src/Symfony/Component/CssSelector/Parser.php index 6861f80899b6a..4e80fc6098d4f 100644 --- a/src/Symfony/Component/CssSelector/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser.php @@ -25,7 +25,16 @@ class Parser { /** + * Translate a CSS expression to its XPath equivalent. + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * * @throws SyntaxError When got None for xpath expression + * + * @param mixed $cssExpr The CSS expression. + * @param string $prefix An optional prefix for the XPath expression. + * + * @return string */ static public function cssToXpath($cssExpr, $prefix = 'descendant-or-self::') { @@ -62,7 +71,14 @@ static public function cssToXpath($cssExpr, $prefix = 'descendant-or-self::') } /** + * Parse an expression and return the Node object that represents + * the parsed expression. + * * @throws \Exception When tokenizer throws it while parsing + * + * @param string $string The expression to parse + * + * @return Node\NodeInterface */ public function parse($string) { @@ -79,6 +95,14 @@ public function parse($string) } } + /** + * Parse a selector group contained in $stream and return + * the Node object that represents the expression. + * + * @param TokenStream $stream The stream to parse. + * + * @return Node\NodeInterface + */ protected function parseSelectorGroup($stream) { $result = array(); @@ -99,7 +123,14 @@ protected function parseSelectorGroup($stream) } /** + * Parse a selector contained in $stream and return the Node + * object that represents it. + * * @throws SyntaxError When expected selector but got something else + * + * @param TokenStrem $stream The stream containing the selector. + * + * @return Node\NodeInterface */ protected function parseSelector($stream) { @@ -128,7 +159,14 @@ protected function parseSelector($stream) } /** + * Parse a simple selector (the current token) from $stream and return + * the resulting Node object. + * * @throws SyntaxError When expected symbol but got something else + * + * @param TokenStream The stream containing the selector. + * + * @return Node\NodeInterface */ protected function parseSimpleSelector($stream) { @@ -228,7 +266,16 @@ protected function parseSimpleSelector($stream) } /** + * Parse an attribute from a selector contained in $stream and return + * the resulting AttribNode object. + * * @throws SyntaxError When encountered unexpected selector + * + * @param Node\NodeInterface $selector The selector object whose attribute + * is to be parsed. + * @param TokenStream $strem The container token stream. + * + * @return Node\AttribNode */ protected function parseAttrib($selector, $stream) { diff --git a/src/Symfony/Component/CssSelector/Token.php b/src/Symfony/Component/CssSelector/Token.php index 0dcb750167045..176617ac77850 100644 --- a/src/Symfony/Component/CssSelector/Token.php +++ b/src/Symfony/Component/CssSelector/Token.php @@ -25,6 +25,13 @@ class Token protected $value; protected $position; + /** + * Constructor. + * + * @param string $type The type of this token. + * @param mixed $value The value of this token. + * @param int $position The order of this token. + */ public function __construct($type, $value, $position) { $this->type = $type; @@ -32,16 +39,33 @@ public function __construct($type, $value, $position) $this->position = $position; } + /** + * Get a string representation of this token. + * + * @return string + */ public function __toString() { return (string) $this->value; } + /** + * Answer whether this token's type equals to $type. + * + * @param string $type The type to test against this token's one. + * + * @return bool + */ public function isType($type) { return $this->type == $type; } + /** + * Get the position of this token. + * + * @return int + */ public function getPosition() { return $this->position; diff --git a/src/Symfony/Component/CssSelector/TokenStream.php b/src/Symfony/Component/CssSelector/TokenStream.php index 5e262530aedcd..c6e62f312d6fb 100644 --- a/src/Symfony/Component/CssSelector/TokenStream.php +++ b/src/Symfony/Component/CssSelector/TokenStream.php @@ -27,6 +27,12 @@ class TokenStream protected $peeked; protected $peeking; + /** + * Constructor. + * + * @param array $tokens The tokens that make the stream. + * @param mixed $source The source of the stream. + */ public function __construct($tokens, $source = null) { $this->used = array(); @@ -36,11 +42,23 @@ public function __construct($tokens, $source = null) $this->peeking = false; } + /** + * Get the tokens that have already been visited in this stream. + * + * @return array + */ public function getUsed() { return $this->used; } + /** + * Get the next token in the stream or null if there is none. + * Note that if this stream was set to be peeking its behavior + * will be restored to not peeking after this operation. + * + * @return mixed + */ public function next() { if ($this->peeking) { @@ -60,6 +78,16 @@ public function next() return $next; } + /** + * Peek for the next token in this stream. This means that the next token + * will be returned but it won't be considered as used (visited) until the + * next() method is invoked. + * If there are no remaining tokens null will be returned. + * + * @see next() + * + * @return mixed + */ public function peek() { if (!$this->peeking) { diff --git a/src/Symfony/Component/CssSelector/Tokenizer.php b/src/Symfony/Component/CssSelector/Tokenizer.php index 339eed25e4821..5980f5a9f3eea 100644 --- a/src/Symfony/Component/CssSelector/Tokenizer.php +++ b/src/Symfony/Component/CssSelector/Tokenizer.php @@ -21,6 +21,14 @@ */ class Tokenizer { + /** + * Take a CSS selector and return an array holding the Tokens + * it contains. + * + * @param string $s The selector to lex. + * + * @return array Token[] + */ public function tokenize($s) { if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { @@ -95,7 +103,16 @@ public function tokenize($s) } /** + * Tokenize a quoted string (i.e. 'A string quoted with \' characters'), + * and return an array holding the unquoted string contained by $s and + * the new position from which tokenizing should take over. + * * @throws SyntaxError When expected closing is not found + * + * @param string $s The selector string containing the quoted string. + * @param int $pos The starting position for the quoted string. + * + * @return array */ protected function tokenizeEscapedString($s, $pos) { @@ -125,7 +142,13 @@ protected function tokenizeEscapedString($s, $pos) } /** + * Unescape a string literal and return the unescaped string. + * * @throws SyntaxError When invalid escape sequence is found + * + * @param string $literal The string literal to unescape. + * + * @return string */ protected function unescapeStringLiteral($literal) { @@ -143,7 +166,16 @@ protected function unescapeStringLiteral($literal) } /** + * Lex selector $s and return the an array holding the name of the symbol + * contained in it and the new position from which tokenizing should take + * over. + * * @throws SyntaxError When Unexpected symbol is found + * + * @param string $s The selector string. + * @param int $pos The position in $s at which the symbol starts. + * + * @return array */ protected function tokenizeSymbol($s, $pos) { diff --git a/src/Symfony/Component/CssSelector/XPathExpr.php b/src/Symfony/Component/CssSelector/XPathExpr.php index 52262e6efcf7c..cd32a8cc43b34 100644 --- a/src/Symfony/Component/CssSelector/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPathExpr.php @@ -27,6 +27,15 @@ class XPathExpr protected $condition; protected $starPrefix; + /** + * Constructor. + * + * @param string $prefix Prefix for the XPath expression. + * @param string $path Actual path of the expression. + * @param string $element The element in the expression. + * @param string $condition A condition for the expression. + * @param bool $starPrefix Indicates whether to use a star prefix. + */ public function __construct($prefix = null, $path = null, $element = '*', $condition = null, $starPrefix = false) { $this->prefix = $prefix; @@ -36,31 +45,61 @@ public function __construct($prefix = null, $path = null, $element = '*', $condi $this->starPrefix = $starPrefix; } + /** + * Get the prefix of this XPath expression. + * + * @return string + */ public function getPrefix() { return $this->prefix; } + /** + * Get the path of this XPath expression. + * + * @return string + */ public function getPath() { return $this->path; } + /** + * Answer whether this XPath expression has a star prefix. + * + * @return bool + */ public function hasStarPrefix() { return $this->starPrefix; } + /** + * Get the element of this XPath expression. + * + * @return string + */ public function getElement() { return $this->element; } + /** + * Get the condition of this XPath expression. + * + * @return string + */ public function getCondition() { return $this->condition; } + /** + * Get a string representation for this XPath expression. + * + * @return string + */ public function __toString() { $path = ''; @@ -81,6 +120,12 @@ public function __toString() return $path; } + /** + * Add a condition to this XPath expression. + * Any pre-existant condition will be ANDed to it. + * + * @param string $condition The condition to add. + */ public function addCondition($condition) { if ($this->condition) { @@ -90,6 +135,12 @@ public function addCondition($condition) } } + /** + * Add a prefix to this XPath expression. + * It will be prepended to any pre-existant prefixes. + * + * @param string $prefix The prefix to add. + */ public function addPrefix($prefix) { if ($this->prefix) { @@ -99,6 +150,11 @@ public function addPrefix($prefix) } } + /** + * Add a condition to this XPath expression using the name of the element + * as the desired value. + * This method resets the element to '*'. + */ public function addNameTest() { if ($this->element == '*') { @@ -110,6 +166,11 @@ public function addNameTest() $this->element = '*'; } + /** + * Add a star prefix to this XPath expression. + * This method will prepend a '*' to the path and set the star prefix flag + * to true. + */ public function addStarPrefix() { /* @@ -125,6 +186,14 @@ public function addStarPrefix() $this->starPrefix = true; } + /** + * Join this XPath expression with $other (another XPath expression) using + * $combiner to join them. + * + * @param string $combiner The combiner string. + * @param XPathExpr $other The other XPath expression to combine with + * this one. + */ public function join($combiner, $other) { $prefix = (string) $this; @@ -143,6 +212,13 @@ public function join($combiner, $other) $this->condition = $other->GetCondition(); } + /** + * Get an XPath literal for $s. + * + * @param mixed $s Can either be a Node\ElementNode or a string. + * + * @return string + */ static public function xpathLiteral($s) { if ($s instanceof Node\ElementNode) { diff --git a/src/Symfony/Component/CssSelector/XPathExprOr.php b/src/Symfony/Component/CssSelector/XPathExprOr.php index c5347ba1306e6..21aa5a95494a8 100644 --- a/src/Symfony/Component/CssSelector/XPathExprOr.php +++ b/src/Symfony/Component/CssSelector/XPathExprOr.php @@ -23,12 +23,23 @@ */ class XPathExprOr extends XPathExpr { + /** + * Constructor. + * + * @param array $items The items in the expression. + * @param string $prefix Optional prefix for the expression. + */ public function __construct($items, $prefix = null) { $this->items = $items; $this->prefix = $prefix; } + /** + * Get a string representation of this |'d expression. + * + * @return string + */ public function __toString() { $prefix = $this->prefix; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php index 4b93f9ac0f3c3..dc2654e77294c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php @@ -41,7 +41,7 @@ public function process(ContainerBuilder $container) // non-synthetic, non-abstract service has class if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) { - if ($definition->getFactoryService()) { + if ($definition->getFactoryClass() || $definition->getFactoryService()) { throw new \RuntimeException(sprintf( 'Please add the class to service "%s" even if it is constructed ' .'by a factory since we might need to add method calls based on ' diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index a0cc8213f541f..d3961ca57432b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -49,8 +49,9 @@ protected function resolveDefinition($id, DefinitionDecorator $definition) $def->setClass($parentDef->getClass()); $def->setArguments($parentDef->getArguments()); $def->setMethodCalls($parentDef->getMethodCalls()); - $def->setFactoryService($parentDef->getFactoryService()); + $def->setFactoryClass($parentDef->getFactoryClass()); $def->setFactoryMethod($parentDef->getFactoryMethod()); + $def->setFactoryService($parentDef->getFactoryService()); $def->setConfigurator($parentDef->getConfigurator()); $def->setFile($parentDef->getFile()); $def->setPublic($parentDef->isPublic()); @@ -60,6 +61,9 @@ protected function resolveDefinition($id, DefinitionDecorator $definition) if (isset($changes['class'])) { $def->setClass($definition->getClass()); } + if (isset($changes['factory_class'])) { + $def->setFactoryClass($definition->getFactoryClass()); + } if (isset($changes['factory_method'])) { $def->setFactoryMethod($definition->getFactoryMethod()); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php index 1373665cd0391..2fecee1b6694e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container) $loaded = false; foreach ($container->getInterfaceInjectors() as $injector) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass() || null !== $definition->getFactoryService()) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php index 9a9fc165f8716..1b9cbdfdc1291 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php @@ -2,23 +2,117 @@ namespace Symfony\Component\DependencyInjection\Configuration; -use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\Configuration\Exception\DuplicateKeyException; use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException; +use Symfony\Component\DependencyInjection\Configuration\Exception\UnsetKeyException; +use Symfony\Component\DependencyInjection\Extension\Extension; +/** + * Represents an ARRAY node in the config tree. + * + * @author Johannes M. Schmitt + */ class ArrayNode extends BaseNode implements PrototypeNodeInterface { - protected $normalizeTransformations; + protected $xmlRemappings; protected $children; protected $prototype; protected $keyAttribute; + protected $allowFalse; + protected $allowNewKeys; + protected $addIfNotSet; + protected $minNumberOfElements; + protected $performDeepMerging; - public function __construct($name, NodeInterface $parent = null, array $beforeTransformations = array(), array $afterTransformations = array(), array $normalizeTransformations = array(), $keyAttribute = null) + public function __construct($name, NodeInterface $parent = null) { - parent::__construct($name, $parent, $beforeTransformations, $afterTransformations); + parent::__construct($name, $parent); $this->children = array(); - $this->normalizeTransformations = $normalizeTransformations; - $this->keyAttribute = $keyAttribute; + $this->xmlRemappings = array(); + $this->allowFalse = false; + $this->addIfNotSet = false; + $this->allowNewKeys = true; + $this->performDeepMerging = true; + $this->minNumberOfElements = 0; + } + + /** + * Sets the xml remappings that should be performed. + * + * @param array $remappings an array of the form array(array(string, string)) + * @return void + */ + public function setXmlRemappings(array $remappings) + { + $this->xmlRemappings = $remappings; + } + + /** + * Sets the minimum number of elements that a prototype based node must + * contain. By default this is zero, meaning no elements. + * + * @param integer $number + * @return void + */ + public function setMinNumberOfElements($number) + { + $this->minNumberOfElements = $number; + } + + /** + * The name of the attribute that should be used as key. + * + * This is only relevant for XML configurations, and only in combination + * with a prototype based node. + * + * @param string $attribute + * @return void + */ + public function setKeyAttribute($attribute) + { + $this->keyAttribute = $attribute; + } + + /** + * Sets whether to add default values for this array if it has not been + * defined in any of the configuration files. + * + * @param Boolean $boolean + * @return void + */ + public function setAddIfNotSet($boolean) + { + $this->addIfNotSet = (Boolean) $boolean; + } + + /** + * Sets whether false is allowed as value indicating that the array should + * be unset. + * + * @param Boolean $allow + * @return void + */ + public function setAllowFalse($allow) + { + $this->allowFalse = (Boolean) $allow; + } + + /** + * Sets whether new keys can be defined in subsequent configurations. + * + * @param Boolean $allow + * @return void + */ + public function setAllowNewKeys($allow) + { + $this->allowNewKeys = (Boolean) $allow; + } + + public function setPerformDeepMerging($boolean) + { + $this->performDeepMerging = (Boolean) $boolean; } public function setName($name) @@ -26,10 +120,41 @@ public function setName($name) $this->name = $name; } + public function hasDefaultValue() + { + if (null !== $this->prototype) { + return true; + } + + return $this->addIfNotSet; + } + + public function getDefaultValue() + { + if (!$this->hasDefaultValue()) { + throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); + } + + if (null !== $this->prototype) { + return array(); + } + + $defaults = array(); + foreach ($this->children as $name => $child) { + if (!$child->hasDefaultValue()) { + continue; + } + + $defaults[$name] = $child->getDefaultValue(); + } + + return $defaults; + } + public function setPrototype(PrototypeNodeInterface $node) { if (count($this->children) > 0) { - throw new \RuntimeException('An ARRAY node must either have concrete children, or a prototype node.'); + throw new \RuntimeException($this->getPath().': An ARRAY node must either have concrete children, or a prototype node.'); } $this->prototype = $node; @@ -51,9 +176,65 @@ public function addChild(NodeInterface $node) $this->children[$name] = $node; } + protected function finalizeValue($value) + { + if (false === $value) { + throw new UnsetKeyException(sprintf( + 'Unsetting key for path "%s", value: %s', + $this->getPath(), + json_encode($value) + )); + } + + if (null !== $this->prototype) { + foreach ($value as $k => $v) { + try { + $value[$k] = $this->prototype->finalize($v); + } catch (UnsetKeyException $unset) { + unset($value[$k]); + } + } + + if (count($value) < $this->minNumberOfElements) { + throw new InvalidConfigurationException(sprintf( + 'You must define at least %d element(s) for path "%s".', + $this->minNumberOfElements, + $this->getPath() + )); + } + + return $value; + } + + foreach ($this->children as $name => $child) { + if (!array_key_exists($name, $value)) { + if ($child->isRequired()) { + throw new InvalidConfigurationException(sprintf( + 'The node at path "%s" must be configured.', + $this->getPath() + )); + } + + if ($child->hasDefaultValue()) { + $value[$name] = $child->getDefaultValue(); + } + + continue; + } + + try { + $value[$name] = $child->finalize($value[$name]); + } catch (UnsetKeyException $unset) { + unset($value[$name]); + } + } + + return $value; + } + protected function validateType($value) { - if (!is_array($value)) { + if (!is_array($value) && (!$this->allowFalse || false !== $value)) { throw new InvalidTypeException(sprintf( 'Invalid type for path "%s". Expected array, but got %s', $this->getPath(), @@ -64,7 +245,11 @@ protected function validateType($value) protected function normalizeValue($value) { - foreach ($this->normalizeTransformations as $transformation) { + if (false === $value) { + return $value; + } + + foreach ($this->xmlRemappings as $transformation) { list($singular, $plural) = $transformation; if (!isset($value[$singular])) { @@ -77,8 +262,24 @@ protected function normalizeValue($value) if (null !== $this->prototype) { $normalized = array(); foreach ($value as $k => $v) { - if (null !== $this->keyAttribute && is_array($v) && isset($v[$this->keyAttribute])) { - $k = $v[$this->keyAttribute]; + if (null !== $this->keyAttribute && is_array($v)) { + if (!isset($v[$this->keyAttribute]) && is_int($k)) { + throw new InvalidConfigurationException(sprintf( + 'You must set a "%s" attribute for path "%s".', + $this->keyAttribute, + $this->getPath() + )); + } else if (isset($v[$this->keyAttribute])) { + $k = $v[$this->keyAttribute]; + } + + if (array_key_exists($k, $normalized)) { + throw new DuplicateKeyException(sprintf( + 'Duplicate key "%s" for path "%s".', + $k, + $this->getPath() + )); + } } $this->prototype->setName($k); @@ -103,4 +304,50 @@ protected function normalizeValue($value) return $normalized; } -} + + protected function mergeValues($leftSide, $rightSide) + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // no conflict + if (!array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + throw new InvalidConfigurationException(sprintf( + 'You are not allowed to define new elements for path "%s". ' + .'Please define all elements for this path in one config file.', + $this->getPath() + )); + } + + $leftSide[$k] = $v; + continue; + } + + try { + if (null !== $this->prototype) { + $this->prototype->setName($k); + $leftSide[$k] = $this->prototype->merge($leftSide[$k], $v); + } else { + if (!isset($this->children[$k])) { + throw new \RuntimeException('merge() expects a normalized config array.'); + } + + $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v); + } + } catch (UnsetKeyException $unset) { + unset($leftSide[$k]); + } + } + + return $leftSide; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php b/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php index 69f5847fbcd74..b2a78e9e910d5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php @@ -2,15 +2,25 @@ namespace Symfony\Component\DependencyInjection\Configuration; +use Symfony\Component\DependencyInjection\Configuration\Exception\Exception; +use Symfony\Component\DependencyInjection\Configuration\Exception\ForbiddenOverwriteException; + +/** + * The base node class + * + * @author Johannes M. Schmitt + */ abstract class BaseNode implements NodeInterface { protected $name; protected $parent; - protected $beforeTransformations; - protected $afterTransformations; - protected $nodeFactory; + protected $normalizationClosures; + protected $finalValidationClosures; + protected $allowOverwrite; + protected $required; + protected $equivalentValues; - public function __construct($name, NodeInterface $parent = null, $beforeTransformations = array(), $afterTransformations = array()) + public function __construct($name, NodeInterface $parent = null) { if (false !== strpos($name, '.')) { throw new \InvalidArgumentException('The name must not contain ".".'); @@ -18,8 +28,41 @@ public function __construct($name, NodeInterface $parent = null, $beforeTransfor $this->name = $name; $this->parent = $parent; - $this->beforeTransformations = $beforeTransformations; - $this->afterTransformations = $afterTransformations; + $this->normalizationClosures = array(); + $this->finalValidationClosures = array(); + $this->allowOverwrite = true; + $this->required = false; + $this->equivalentValues = array(); + } + + public function addEquivalentValue($originalValue, $equivalentValue) + { + $this->equivalentValues[] = array($originalValue, $equivalentValue); + } + + public function setRequired($boolean) + { + $this->required = (Boolean) $boolean; + } + + public function setAllowOverwrite($allow) + { + $this->allowOverwrite = (Boolean) $allow; + } + + public function setNormalizationClosures(array $closures) + { + $this->normalizationClosures = $closures; + } + + public function setFinalValidationClosures(array $closures) + { + $this->finalValidationClosures = $closures; + } + + public function isRequired() + { + return $this->required; } public function getName() @@ -38,22 +81,64 @@ public function getPath() return $path; } + public final function merge($leftSide, $rightSide) + { + if (!$this->allowOverwrite) { + throw new ForbiddenOverwriteException(sprintf( + 'Configuration path "%s" cannot be overwritten. You have to ' + .'define all options for this path, and any of its sub-paths in ' + .'one configuration section.', + $this->getPath() + )); + } + + $this->validateType($leftSide); + $this->validateType($rightSide); + + return $this->mergeValues($leftSide, $rightSide); + } + public final function normalize($value) { - // run before transformations - foreach ($this->beforeTransformations as $transformation) { - $value = $transformation($value); + // run custom normalization closures + foreach ($this->normalizationClosures as $closure) { + $value = $closure($value); + } + + // replace value with their equivalent + foreach ($this->equivalentValues as $data) { + if ($data[0] === $value) { + $value = $data[1]; + } } // validate type $this->validateType($value); // normalize value - $value = $this->normalizeValue($value); + return $this->normalizeValue($value); + } + + public final function finalize($value) + { + $this->validateType($value); + + $value = $this->finalizeValue($value); - // run after transformations - foreach ($this->afterTransformations as $transformation) { - $value = $transformation($value); + // Perform validation on the final value if a closure has been set. + // The closure is also allowed to return another value. + foreach ($this->finalValidationClosures as $closure) { + try { + $value = $closure($value); + } catch (Exception $correctEx) { + throw $correctEx; + } catch (\Exception $invalid) { + throw new InvalidConfigurationException(sprintf( + 'Invalid configuration for path "%s": %s', + $this->getPath(), + $invalid->getMessage() + ), $invalid->getCode(), $invalid); + } } return $value; @@ -61,4 +146,6 @@ public final function normalize($value) abstract protected function validateType($value); abstract protected function normalizeValue($value); + abstract protected function mergeValues($leftSide, $rightSide); + abstract protected function finalizeValue($value); } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php b/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php new file mode 100644 index 0000000000000..3960e0e756b5c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php @@ -0,0 +1,21 @@ +getPath(), + json_encode($value) + )); + } + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php index c8f2e0cbdd848..1e29a6ed72778 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php @@ -2,6 +2,11 @@ namespace Symfony\Component\DependencyInjection\Configuration\Builder; +/** + * This class builds an if expression. + * + * @author Johannes M. Schmitt + */ class ExprBuilder { public $parent; @@ -13,8 +18,12 @@ public function __construct($parent) $this->parent = $parent; } - public function ifTrue(\Closure $closure) + public function ifTrue(\Closure $closure = null) { + if (null === $closure) { + $closure = function($v) { return true === $v; }; + } + $this->ifPart = $closure; return $this; @@ -48,6 +57,31 @@ public function then(\Closure $closure) return $this; } + public function thenReplaceKeyWithAttribute($attribute) + { + $this->thenPart = function($v) { + $newValue = array(); + foreach ($v as $k => $oldValue) { + if (is_array($oldValue) && isset($oldValue['id'])) { + $k = $oldValue['id']; + } + + $newValue[$k] = $oldValue; + } + + return $newValue; + }; + + return $this; + } + + public function thenEmptyArray() + { + $this->thenPart = function($v) { return array(); }; + + return $this; + } + public function end() { if (null === $this->ifPart) { diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php new file mode 100644 index 0000000000000..5ac10001dbd44 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php @@ -0,0 +1,41 @@ + + */ +class MergeBuilder +{ + public $parent; + public $allowFalse; + public $allowOverwrite; + + public function __construct($parent) + { + $this->parent = $parent; + $this->allowFalse = false; + $this->allowOverwrite = true; + } + + public function allowUnset($allow = true) + { + $this->allowFalse = $allow; + + return $this; + } + + public function denyOverwrite($deny = true) + { + $this->allowOverwrite = !$deny; + + return $this; + } + + public function end() + { + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php index 0c63068d71433..aa19de0e61139 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php @@ -2,6 +2,11 @@ namespace Symfony\Component\DependencyInjection\Configuration\Builder; +/** + * This class provides a fluent interface for building a config tree. + * + * @author Johannes M. Schmitt + */ class NodeBuilder { /************ @@ -13,9 +18,20 @@ class NodeBuilder public $parent; public $children; public $prototype; - public $normalizeTransformations; - public $beforeTransformations; - public $afterTransformations; + public $normalization; + public $merge; + public $finalization; + public $defaultValue; + public $default; + public $addDefaults; + public $required; + public $atLeastOne; + public $allowNewKeys; + public $allowEmptyValue; + public $nullEquivalent; + public $trueEquivalent; + public $falseEquivalent; + public $performDeepMerging; public function __construct($name, $type, $parent = null) { @@ -23,10 +39,28 @@ public function __construct($name, $type, $parent = null) $this->type = $type; $this->parent = $parent; - $this->children = - $this->beforeTransformations = - $this->afterTransformations = - $this->normalizeTransformations = array(); + $this->default = false; + $this->required = false; + $this->addDefaults = false; + $this->allowNewKeys = true; + $this->atLeastOne = false; + $this->allowEmptyValue = true; + $this->children = array(); + $this->performDeepMerging = true; + + if ('boolean' === $type) { + $this->nullEquivalent = true; + } else if ('array' === $type) { + $this->nullEquivalent = array(); + } + + if ('array' === $type) { + $this->trueEquivalent = array(); + } else { + $this->trueEquivalent = true; + } + + $this->falseEquivalent = false; } /**************************** @@ -35,54 +69,178 @@ public function __construct($name, $type, $parent = null) public function node($name, $type) { - $node = new NodeBuilder($name, $type, $this); + $node = new static($name, $type, $this); return $this->children[$name] = $node; } - public function normalize($key, $plural = null) + public function arrayNode($name) { - if (null === $plural) { - $plural = $key.'s'; - } + return $this->node($name, 'array'); + } + + public function scalarNode($name) + { + return $this->node($name, 'scalar'); + } + + public function booleanNode($name) + { + return $this->node($name, 'boolean'); + } - $this->normalizeTransformations[] = array($key, $plural); + public function defaultValue($value) + { + $this->default = true; + $this->defaultValue = $value; return $this; } - public function key($name) + public function isRequired() { - $this->key = $name; + $this->required = true; + + return $this; + } + + public function containsNameValuePairsWithKeyAttribute($attribute) + { + $this->beforeNormalization() + ->ifArray() + ->thenReplaceKeyWithAttribute($attribute) + ; + + $this->useAttributeAsKey($attribute); return $this; } - public function before(\Closure $closure = null) + public function requiresAtLeastOneElement() { - if (null !== $closure) { - $this->beforeTransformations[] = $closure; + $this->atLeastOne = true; + + return $this; + } + + public function treatNullLike($value) + { + $this->nullEquivalent = $value; + + return $this; + } + + public function treatTrueLike($value) + { + $this->trueEquivalent = $value; + + return $this; + } + + public function treatFalseLike($value) + { + $this->falseEquivalent = $value; + + return $this; + } + + public function defaultNull() + { + return $this->defaultValue(null); + } + + public function defaultTrue() + { + return $this->defaultValue(true); + } + + public function defaultFalse() + { + return $this->defaultValue(false); + } - return $this; + public function addDefaultsIfNotSet() + { + $this->addDefaults = true; + + return $this; + } + + public function disallowNewKeysInSubsequentConfigs() + { + $this->allowNewKeys = false; + + return $this; + } + + protected function normalization() + { + if (null === $this->normalization) { + $this->normalization = new NormalizationBuilder($this); } - return $this->beforeTransformations[] = new ExprBuilder($this); + return $this->normalization; } - public function prototype($type) + public function beforeNormalization() { - return $this->prototype = new NodeBuilder(null, $type, $this); + return $this->normalization()->before(); } - public function after(\Closure $closure = null) + public function fixXmlConfig($singular, $plural = null) { - if (null !== $closure) { - $this->afterTransformations[] = $closure; + $this->normalization()->remap($singular, $plural); - return $this; + return $this; + } + + public function useAttributeAsKey($name) + { + $this->key = $name; + + return $this; + } + + protected function merge() + { + if (null === $this->merge) { + $this->merge = new MergeBuilder($this); } - return $this->afterTransformations[] = new ExprBuilder($this); + return $this->merge; + } + + public function cannotBeOverwritten($deny = true) + { + $this->merge()->denyOverwrite($deny); + + return $this; + } + + public function cannotBeEmpty() + { + $this->allowEmptyValue = false; + + return $this; + } + + public function canBeUnset($allow = true) + { + $this->merge()->allowUnset($allow); + + return $this; + } + + public function prototype($type) + { + return $this->prototype = new static(null, $type, $this); + } + + public function performNoDeepMerging() + { + $this->performDeepMerging = false; + + return $this; } public function end() diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php new file mode 100644 index 0000000000000..96e76cb586d57 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php @@ -0,0 +1,48 @@ + + */ +class NormalizationBuilder +{ + public $parent; + public $before; + public $remappings; + + public function __construct($parent) + { + $this->parent = $parent; + + $this->keys = false; + + $this->remappings = + $this->before = + $this->after = array(); + } + + public function remap($key, $plural = null) + { + if (null === $plural) { + $plural = $key.'s'; + } + + $this->remappings[] = array($key, $plural); + + return $this; + } + + public function before(\Closure $closure = null) + { + if (null !== $closure) { + $this->before[] = $closure; + + return $this; + } + + return $this->before[] = new ExprBuilder($this->parent); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php index 93f48bdf1936b..e24a23cb4dce5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php @@ -2,9 +2,18 @@ namespace Symfony\Component\DependencyInjection\Configuration\Builder; +use Symfony\Component\DependencyInjection\Configuration\BaseNode; + +use Symfony\Component\DependencyInjection\Configuration\BooleanNode; + use Symfony\Component\DependencyInjection\Configuration\ArrayNode; use Symfony\Component\DependencyInjection\Configuration\ScalarNode; +/** + * This is the entry class for building your own config tree. + * + * @author Johannes M. Schmitt + */ class TreeBuilder { protected $root; @@ -32,9 +41,6 @@ public function buildTree() protected function createConfigNode(NodeBuilder $node) { - $node->beforeTransformations = $this->buildExpressions($node->beforeTransformations); - $node->afterTransformations = $this->buildExpressions($node->afterTransformations); - $method = 'create'.$node->type.'ConfigNode'; if (!method_exists($this, $method)) { throw new \RuntimeException(sprintf('Unknown node type: "%s"', $node->type)); @@ -43,14 +49,77 @@ protected function createConfigNode(NodeBuilder $node) return $this->$method($node); } + protected function createBooleanConfigNode(NodeBuilder $node) + { + $configNode = new BooleanNode($node->name, $node->parent); + $this->configureScalarNode($configNode, $node); + + return $configNode; + } + protected function createScalarConfigNode(NodeBuilder $node) { - return new ScalarNode($node->name, $node->parent, $node->beforeTransformations, $node->afterTransformations); + $configNode = new ScalarNode($node->name, $node->parent); + $this->configureScalarNode($configNode, $node); + + return $configNode; + } + + protected function configureScalarNode(ScalarNode $configNode, NodeBuilder $node) + { + if (null !== $node->normalization) { + $configNode->setNormalizationClosures( + $this->buildExpressions($node->normalization->before) + ); + } + + if (null !== $node->merge) { + $configNode->setAllowOverwrite($node->merge->allowOverwrite); + } + + if (true === $node->default) { + $configNode->setDefaultValue($node->defaultValue); + } + + if (false === $node->allowEmptyValue) { + $configNode->setAllowEmptyValue($node->allowEmptyValue); + } + + $configNode->addEquivalentValue(null, $node->nullEquivalent); + $configNode->addEquivalentValue(true, $node->trueEquivalent); + $configNode->addEquivalentValue(false, $node->falseEquivalent); } protected function createArrayConfigNode(NodeBuilder $node) { - $configNode = new ArrayNode($node->name, $node->parent, $node->beforeTransformations, $node->afterTransformations, $node->normalizeTransformations, $node->key); + $configNode = new ArrayNode($node->name, $node->parent); + $configNode->setAddIfNotSet($node->addDefaults); + $configNode->setAllowNewKeys($node->allowNewKeys); + $configNode->addEquivalentValue(null, $node->nullEquivalent); + $configNode->addEquivalentValue(true, $node->trueEquivalent); + $configNode->addEquivalentValue(false, $node->falseEquivalent); + $configNode->setPerformDeepMerging($node->performDeepMerging); + + if (null !== $node->key) { + $configNode->setKeyAttribute($node->key); + } + + if (true === $node->atLeastOne) { + $configNode->setMinNumberOfElements(1); + } + + if (null !== $node->normalization) { + $configNode->setNormalizationClosures( + $this->buildExpressions($node->normalization->before) + ); + + $configNode->setXmlRemappings($node->normalization->remappings); + } + + if (null !== $node->merge) { + $configNode->setAllowOverwrite($node->merge->allowOverwrite); + $configNode->setAllowFalse($node->merge->allowFalse); + } foreach ($node->children as $child) { $child->parent = $configNode; diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php new file mode 100644 index 0000000000000..7da500ba5693f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php @@ -0,0 +1,13 @@ + + */ +class DuplicateKeyException extends InvalidConfigurationException +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php index e5a464b27694d..c669089f472b4 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php @@ -2,6 +2,11 @@ namespace Symfony\Component\DependencyInjection\Configuration\Exception; +/** + * Base exception for all configuration exceptions + * + * @author Johannes M. Schmitt + */ class Exception extends \RuntimeException { } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php new file mode 100644 index 0000000000000..0f7537747ec31 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php @@ -0,0 +1,13 @@ + + */ +class ForbiddenOverwriteException extends InvalidConfigurationException +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000000000..71f3ffbd5282e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php @@ -0,0 +1,13 @@ + + */ +class InvalidConfigurationException extends Exception +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php index 436c80fa80add..3cdbc52439c58 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php @@ -5,8 +5,8 @@ /** * This exception is thrown if an invalid type is encountered. * - * @author johannes + * @author Johannes M. Schmitt */ -class InvalidTypeException extends Exception +class InvalidTypeException extends InvalidConfigurationException { } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php new file mode 100644 index 0000000000000..2388b134b42bb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php @@ -0,0 +1,13 @@ + + */ +class UnsetKeyException extends Exception +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php b/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php index a5e8611c637d1..70271946b46b5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php @@ -2,9 +2,21 @@ namespace Symfony\Component\DependencyInjection\Configuration; +/** + * Common Interface among all nodes. + * + * In most cases, it is better to inherit from BaseNode instead of implementing + * this interface yourself. + * + * @author Johannes M. Schmitt + */ interface NodeInterface { function getName(); function getPath(); + function isRequired(); + function hasDefaultValue(); + function getDefaultValue(); function normalize($value); + function merge($leftSide, $rightSide); } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Processor.php b/src/Symfony/Component/DependencyInjection/Configuration/Processor.php new file mode 100644 index 0000000000000..cdabd29a030fe --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Processor.php @@ -0,0 +1,26 @@ + + */ +class Processor +{ + public function process(NodeInterface $configTree, array $configs) + { + $configs = Extension::normalizeKeys($configs); + + $currentConfig = array(); + foreach ($configs as $config) { + $config = $configTree->normalize($config); + $currentConfig = $configTree->merge($currentConfig, $config); + } + + return $configTree->finalize($currentConfig); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php index cdcebe3dac6a7..fd871c3864590 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php @@ -2,10 +2,41 @@ namespace Symfony\Component\DependencyInjection\Configuration; +use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException; +/** + * This node represents a scalar value in the config tree. + * + * @author Johannes M. Schmitt + */ class ScalarNode extends BaseNode implements PrototypeNodeInterface { + protected $defaultValueSet = false; + protected $defaultValue; + protected $allowEmptyValue = true; + + public function setDefaultValue($value) + { + $this->defaultValueSet = true; + $this->defaultValue = $value; + } + + public function hasDefaultValue() + { + return $this->defaultValueSet; + } + + public function getDefaultValue() + { + return $this->defaultValue; + } + + public function setAllowEmptyValue($boolean) + { + $this->allowEmptyValue = (Boolean) $boolean; + } + public function setName($name) { $this->name = $name; @@ -14,7 +45,7 @@ public function setName($name) protected function validateType($value) { if (!is_scalar($value)) { - throw new \InvalidTypeException(sprintf( + throw new InvalidTypeException(sprintf( 'Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), json_encode($value) @@ -22,8 +53,26 @@ protected function validateType($value) } } + protected function finalizeValue($value) + { + if (!$this->allowEmptyValue && empty($value)) { + throw new InvalidConfigurationException(sprintf( + 'The path "%s" cannot contain an empty value, but got %s.', + $this->getPath(), + json_encode($value) + )); + } + + return $value; + } + protected function normalizeValue($value) { return $value; } + + protected function mergeValues($leftSide, $rightSide) + { + return $rightSide; + } } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 8bf948e67b9e4..008ef416c269e 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -695,10 +695,12 @@ protected function createService(Definition $definition, $id) $arguments = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getArguments())); if (null !== $definition->getFactoryMethod()) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass()) { + $factory = $this->getParameterBag()->resolveValue($definition->getFactoryClass()); + } elseif (null !== $definition->getFactoryService()) { $factory = $this->get($this->getParameterBag()->resolveValue($definition->getFactoryService())); } else { - $factory = $this->getParameterBag()->resolveValue($definition->getClass()); + throw new \RuntimeException('Cannot create service from factory method without a factory service or factory class.'); } $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments); diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index fa70eb6d14e55..e3d58cb6f2080 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -20,6 +20,7 @@ class Definition { protected $class; protected $file; + protected $factoryClass; protected $factoryMethod; protected $factoryService; protected $scope; @@ -49,16 +50,41 @@ public function __construct($class = null, array $arguments = array()) $this->abstract = false; } + /** + * Sets the name of the class that acts as a factory using the factory method, + * which will be invoked statically. + * + * @param string $factoryClass The factory class name + * + * @return Definition The current instance + */ + public function setFactoryClass($factoryClass) + { + $this->factoryClass = $factoryClass; + + return $this; + } + + /** + * Gets the factory class. + * + * @return string The factory class name + */ + public function getFactoryClass() + { + return $this->factoryClass; + } + /** * Sets the factory method able to create an instance of this class. * - * @param string $method The method name + * @param string $factoryMethod The factory method name * * @return Definition The current instance */ - public function setFactoryMethod($method) + public function setFactoryMethod($factoryMethod) { - $this->factoryMethod = $method; + $this->factoryMethod = $factoryMethod; return $this; } @@ -74,7 +100,7 @@ public function getFactoryMethod() } /** - * Sets the name of the service that acts as a factory using the constructor method. + * Sets the name of the service that acts as a factory using the factory method. * * @param string $factoryService The factory service id * diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php index cde424c50bf18..e74a1bf7e319c 100644 --- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php +++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php @@ -37,11 +37,11 @@ public function setClass($class) return parent::setClass($class); } - public function setFactoryService($service) + public function setFactoryClass($class) { - $this->changes['factory_service'] = true; + $this->changes['factory_class'] = true; - return parent::setFactoryService($service); + return parent::setFactoryClass($class); } public function setFactoryMethod($method) @@ -51,6 +51,13 @@ public function setFactoryMethod($method) return parent::setFactoryMethod($method); } + public function setFactoryService($service) + { + $this->changes['factory_service'] = true; + + return parent::setFactoryService($service); + } + public function setConfigurator($callable) { $this->changes['configurator'] = true; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 57e984f408bb9..7829b10b1a5fa 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -230,10 +230,12 @@ protected function addServiceInlinedDefinitions($id, $definition) } if (null !== $sDefinition->getFactoryMethod()) { - if (null !== $sDefinition->getFactoryService()) { + if (null !== $sDefinition->getFactoryClass()) { + $code .= sprintf(" \$%s = call_user_func(array(%s, '%s')%s);\n", $name, $this->dumpValue($sDefinition->getFactoryClass()), $sDefinition->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $sDefinition->getFactoryService()) { $code .= sprintf(" \$%s = %s->%s(%s);\n", $name, $this->getServiceCall($sDefinition->getFactoryService()), $sDefinition->getFactoryMethod(), implode(', ', $arguments)); } else { - $code .= sprintf(" \$%s = call_user_func(array(%s, '%s')%s);\n", $name, $class, $sDefinition->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Factory service or factory class must be defined in service definition for '.$id); } } elseif (false !== strpos($class, '$')) { $code .= sprintf(" \$class = %s;\n \$%s = new \$class(%s);\n", $class, $name, implode(', ', $arguments)); @@ -294,10 +296,12 @@ protected function addServiceInstance($id, $definition) } if (null !== $definition->getFactoryMethod()) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass()) { + $code = sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($definition->getFactoryClass()), $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $definition->getFactoryService()) { $code = sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService()), $definition->getFactoryMethod(), implode(', ', $arguments)); } else { - $code = sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $class, $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Factory method requires a factory service or factory class in service definition for '.$id); } } elseif (false !== strpos($class, '$')) { $code = sprintf(" \$class = %s;\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); @@ -404,8 +408,10 @@ protected function addService($id, $definition) $return = ''; if ($definition->isSynthetic()) { $return = sprintf('@throws \RuntimeException always since this service is expected to be injected dynamically'); - } else if ($class = $definition->getClass()) { + } elseif ($class = $definition->getClass()) { $return = sprintf("@return %s A %s instance.", 0 === strpos($class, '%') ? 'Object' : $class, $class); + } elseif ($definition->getFactoryClass()) { + $return = sprintf('@return Object An instance returned by %s::%s().', $definition->getFactoryClass(), $definition->getFactoryMethod()); } elseif ($definition->getFactoryService()) { $return = sprintf('@return Object An instance returned by %s::%s().', $definition->getFactoryService(), $definition->getFactoryMethod()); } @@ -821,10 +827,12 @@ protected function dumpValue($value, $interpolate = true) } if (null !== $value->getFactoryMethod()) { - if (null !== $value->getFactoryService()) { + if (null !== $value->getFactoryClass()) { + return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass()), $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $value->getFactoryService()) { return sprintf("%s->%s(%s)", $this->getServiceCall($value->getFactoryService()), $value->getFactoryMethod(), implode(', ', $arguments)); } else { - return sprintf("call_user_func(array(%s, '%s')%s)", $class, $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Cannot dump definitions which have factory method without factory service or factory class.'); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index ec0a10625ee25..ec39573f83b72 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -143,7 +143,7 @@ protected function parseDefinition($id, $service, $file) $definition = new Definition(); } - foreach (array('class', 'scope', 'public', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) { + foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) { if (isset($service[$key])) { $method = 'set'.str_replace('-', '', $key); $definition->$method((string) $service->getAttributeAsPhp($key)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 65def0455f834..926a274a00300 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -165,6 +165,10 @@ protected function parseDefinition($id, $service, $file) $definition->setAbstract($service['abstract']); } + if (isset($service['factory_class'])) { + $definition->setFactoryClass($service['factory_class']); + } + if (isset($service['factory_method'])) { $definition->setFactoryMethod($service['factory_method']); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index a6ccf726ee44f..35a8a684c9887 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -105,6 +105,7 @@ + diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php index 8fd995d67351f..50464ea2a3759 100644 --- a/src/Symfony/Component/HttpFoundation/File/File.php +++ b/src/Symfony/Component/HttpFoundation/File/File.php @@ -605,7 +605,7 @@ public function getMimeType() */ public function size() { - if (false === ($size = filesize($this->getPath()))) { + if (false === ($size = @filesize($this->getPath()))) { throw new FileException(sprintf('Could not read file size of %s', $this->getPath())); } @@ -623,7 +623,7 @@ protected function doMove($directory, $filename) { $newPath = $directory . DIRECTORY_SEPARATOR . $filename; - if (!rename($this->getPath(), $newPath)) { + if (!@rename($this->getPath(), $newPath)) { throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath)); } diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php index e214c00cd7238..17b5f75a73c71 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -25,6 +25,7 @@ abstract class Bundle extends ContainerAware implements BundleInterface { protected $name; + protected $reflected; /** * Boots the Bundle. @@ -40,6 +41,34 @@ public function shutdown() { } + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return $this->reflected->getNamespaceName(); + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + */ + public function getPath() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return strtr(dirname($this->reflected->getFileName()), '\\', '/'); + } + /** * Returns the bundle parent name. * @@ -67,18 +96,6 @@ final public function getName() return $this->name = false === $pos ? $name : substr($name, $pos + 1); } - /** - * Gets the Bundle directory path. - * - * The path should always be returned as a Unix path (with /). - * - * @return string The Bundle absolute path - */ - final public function getNormalizedPath() - { - return strtr($this->getPath(), '\\', '/'); - } - /** * Finds and registers Dependency Injection Container extensions. * @@ -91,7 +108,7 @@ final public function getNormalizedPath() */ public function registerExtensions(ContainerBuilder $container) { - if (!$dir = realpath($this->getNormalizedPath().'/DependencyInjection')) { + if (!$dir = realpath($this->getPath().'/DependencyInjection')) { return; } @@ -118,7 +135,7 @@ public function registerExtensions(ContainerBuilder $container) */ public function registerCommands(Application $application) { - if (!$dir = realpath($this->getNormalizedPath().'/Command')) { + if (!$dir = realpath($this->getPath().'/Command')) { return; } diff --git a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php index fd55044e5ef5e..ba7f0ab6b647f 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php +++ b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php @@ -49,14 +49,6 @@ function getName(); */ function getNamespace(); - - /** - * Gets the Bundle directory path. - * - * @return string The Bundle absolute path - */ - function getPath(); - /** * Gets the Bundle directory path. * @@ -64,5 +56,5 @@ function getPath(); * * @return string The Bundle absolute path */ - function getNormalizedPath(); + function getPath(); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c46d9bf1a150c..da64b790a76f0 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -237,7 +237,7 @@ public function locateResource($name, $dir = null, $first = true) } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php index 25d4cc1f5f77c..f82ecec163176 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php @@ -157,7 +157,10 @@ protected function exec($db, $query, array $args = array()) foreach ($args as $arg => $val) { $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); } - $stmt->execute(); + $success = $stmt->execute(); + if (!$success) { + throw new \RuntimeException(sprintf('Error executing SQLite query "%s"', $query)); + } } } diff --git a/src/Symfony/Component/HttpKernel/bootstrap.php b/src/Symfony/Component/HttpKernel/bootstrap.php index acdb86346579d..699e1f11a057d 100644 --- a/src/Symfony/Component/HttpKernel/bootstrap.php +++ b/src/Symfony/Component/HttpKernel/bootstrap.php @@ -113,7 +113,7 @@ public function getServiceIds() $ids = array(); $r = new \ReflectionClass($this); foreach ($r->getMethods() as $method) { - if (preg_match('/^get(.+)Service$/', $name = $method->getName(), $match)) { + if (preg_match('/^get(.+)Service$/', $method->getName(), $match)) { $ids[] = self::underscore($match[1]); } } @@ -229,7 +229,6 @@ function getParent(); function getName(); function getNamespace(); function getPath(); - function getNormalizedPath(); } } namespace Symfony\Component\HttpKernel\Bundle @@ -241,12 +240,27 @@ function getNormalizedPath(); abstract class Bundle extends ContainerAware implements BundleInterface { protected $name; + protected $reflected; public function boot() { } public function shutdown() { } + public function getNamespace() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + return $this->reflected->getNamespaceName(); + } + public function getPath() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + return strtr(dirname($this->reflected->getFileName()), '\\', '/'); + } public function getParent() { return null; @@ -260,13 +274,9 @@ final public function getName() $pos = strrpos($name, '\\'); return $this->name = false === $pos ? $name : substr($name, $pos + 1); } - final public function getNormalizedPath() - { - return strtr($this->getPath(), '\\', '/'); - } public function registerExtensions(ContainerBuilder $container) { - if (!$dir = realpath($this->getNormalizedPath().'/DependencyInjection')) { + if (!$dir = realpath($this->getPath().'/DependencyInjection')) { return; } $finder = new Finder(); @@ -279,7 +289,7 @@ public function registerExtensions(ContainerBuilder $container) } public function registerCommands(Application $application) { - if (!$dir = realpath($this->getNormalizedPath().'/Command')) { + if (!$dir = realpath($this->getPath().'/Command')) { return; } $finder = new Finder(); @@ -577,7 +587,7 @@ public function locateResource($name, $dir = null, $first = true) $files[] = $file; } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/src/Symfony/Component/HttpKernel/bootstrap_cache.php b/src/Symfony/Component/HttpKernel/bootstrap_cache.php index 051f3bc25b43c..e1bbfc66aa100 100644 --- a/src/Symfony/Component/HttpKernel/bootstrap_cache.php +++ b/src/Symfony/Component/HttpKernel/bootstrap_cache.php @@ -151,7 +151,7 @@ public function locateResource($name, $dir = null, $first = true) $files[] = $file; } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php b/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php index 3d493f9d21ff2..42f45569dec2c 100644 --- a/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php +++ b/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php @@ -102,9 +102,6 @@ public function testGetResponse() $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); } - /** - * @covers Symfony\Component\BrowserKit\Client::getContent - */ public function testGetContent() { $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php new file mode 100644 index 0000000000000..73d46bbc3853f --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php @@ -0,0 +1,61 @@ +register('a')->setSynthetic(true)->setPublic(false); + + $this->process($container); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessDetectsSyntheticPrototypeDefinitions() + { + $container = new ContainerBuilder(); + $container->register('a')->setSynthetic(true)->setScope(ContainerInterface::SCOPE_PROTOTYPE); + + $this->process($container); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessDetectsNonSyntheticNonAbstractDefinitionWithoutClass() + { + $container = new ContainerBuilder(); + $container->register('a')->setSynthetic(false)->setAbstract(false); + + $this->process($container); + } + + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('a', 'class'); + $container->register('b', 'class')->setSynthetic(true)->setPublic(true); + $container->register('c', 'class')->setAbstract(true); + $container->register('d', 'class')->setSynthetic(true); + + $this->process($container); + } + + protected function process(ContainerBuilder $container) + { + $pass = new CheckDefinitionValidityPass(); + $pass->process($container); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php new file mode 100644 index 0000000000000..da3155493e241 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php @@ -0,0 +1,17 @@ +normalize(false); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php new file mode 100644 index 0000000000000..ab3f5af12babe --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php @@ -0,0 +1,59 @@ +root('config', 'array') + ->node('level1', 'array') + ->canBeUnset() + ->node('level2', 'array') + ->canBeUnset() + ->node('somevalue', 'scalar')->end() + ->node('anothervalue', 'scalar')->end() + ->end() + ->node('level1_scalar', 'scalar')->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'level1' => array( + 'level2' => array( + 'somevalue' => 'foo', + 'anothervalue' => 'bar', + ), + 'level1_scalar' => 'foo', + ), + ); + + $b = array( + 'level1' => array( + 'level2' => false, + ), + ); + + $this->assertEquals(array( + 'level1' => array( + 'level1_scalar' => 'foo', + ), + ), $this->process($tree, array($a, $b))); + } + + protected function process(NodeInterface $tree, array $configs) + { + $processor = new Processor(); + + return $processor->process($tree, $configs); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php new file mode 100644 index 0000000000000..0fab77cc11286 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php @@ -0,0 +1,148 @@ +root('root', 'array') + ->node('foo', 'scalar') + ->merge() + ->denyOverwrite() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'foo' => 'bar', + ); + + $b = array( + 'foo' => 'moo', + ); + + $tree->merge($a, $b); + } + + public function testUnsetKey() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('root', 'array') + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->node('unsettable', 'array') + ->merge()->allowUnset()->end() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->end() + ->node('unsetted', 'array') + ->merge()->allowUnset()->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'foo' => 'bar', + 'unsettable' => array( + 'foo' => 'a', + 'bar' => 'b', + ), + 'unsetted' => false, + ); + + $b = array( + 'foo' => 'moo', + 'bar' => 'b', + 'unsettable' => false, + 'unsetted' => array('a', 'b'), + ); + + $this->assertEquals(array( + 'foo' => 'moo', + 'bar' => 'b', + 'unsettable' => false, + 'unsetted' => array('a', 'b'), + ), $tree->merge($a, $b)); + } + + /** + * @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException + */ + public function testDoesNotAllowNewKeysInSubsequentConfigs() + { + $tb = new TreeBuilder(); + $tree = $tb + ->root('config', 'array') + ->node('test', 'array') + ->disallowNewKeysInSubsequentConfigs() + ->useAttributeAsKey('key') + ->prototype('array') + ->node('value', 'scalar')->end() + ->end() + ->end() + ->end() + ->buildTree(); + + $a = array( + 'test' => array( + 'a' => array('value' => 'foo') + ) + ); + + $b = array( + 'test' => array( + 'b' => array('value' => 'foo') + ) + ); + + $tree->merge($a, $b); + } + + public function testPerformsNoDeepMerging() + { + $tb = new TreeBuilder(); + + $tree = $tb + ->root('config', 'array') + ->node('no_deep_merging', 'array') + ->performNoDeepMerging() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'no_deep_merging' => array( + 'foo' => 'a', + 'bar' => 'b', + ), + ); + + $b = array( + 'no_deep_merging' => array( + 'c' => 'd', + ) + ); + + $this->assertEquals(array( + 'no_deep_merging' => array( + 'c' => 'd', + ) + ), $tree->merge($a, $b)); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php index 4afbdb9eeeb86..9254700eb63ff 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php @@ -15,11 +15,11 @@ public function testNormalizeEncoders($denormalized) $tb = new TreeBuilder(); $tree = $tb ->root('root_name', 'array') - ->normalize('encoder') + ->fixXmlConfig('encoder') ->node('encoders', 'array') - ->key('class') + ->useAttributeAsKey('class') ->prototype('array') - ->before()->ifString()->then(function($v) { return array('algorithm' => $v); })->end() + ->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end() ->node('algorithm', 'scalar')->end() ->end() ->end() @@ -87,7 +87,7 @@ public function testAnonymousKeysArray($denormalized) $tree = $tb ->root('root', 'array') ->node('logout', 'array') - ->normalize('handler') + ->fixXmlConfig('handler') ->node('handlers', 'array') ->prototype('scalar')->end() ->end() diff --git a/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php b/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php index b9d68f072e889..4a14e40664364 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php @@ -247,7 +247,7 @@ public function testCreateServiceFactoryMethod() { $builder = new ContainerBuilder(); $builder->register('bar', 'stdClass'); - $builder->register('foo1', 'FooClass')->setFactoryMethod('getInstance')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); + $builder->register('foo1', 'FooClass')->setFactoryClass('FooClass')->setFactoryMethod('getInstance')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); $builder->setParameter('value', 'bar'); $this->assertTrue($builder->get('foo1')->called, '->createService() calls the factory method to create the service instance'); $this->assertEquals(array('foo' => 'bar', 'bar' => 'foo', $builder->get('bar')), $builder->get('foo1')->arguments, '->createService() passes the arguments to the factory method'); diff --git a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php index ff80fba5d83b8..6c74adfe4a42d 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php @@ -34,8 +34,9 @@ public function getPropertyTests() { return array( array('class', 'class'), - array('factoryService', 'factory_service'), + array('factoryClass', 'factory_class'), array('factoryMethod', 'factory_method'), + array('factoryService', 'factory_service'), array('configurator', 'configurator'), array('file', 'file'), ); diff --git a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php index 41ded4f06bc35..5a6097dcd3156 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php @@ -27,13 +27,18 @@ public function testConstructor() $this->assertEquals(array('foo'), $def->getArguments(), '__construct() takes an optional array of arguments as its second argument'); } - /** - * @covers Symfony\Component\DependencyInjection\Definition::setFactoryMethod - * @covers Symfony\Component\DependencyInjection\Definition::getFactoryMethod - */ - public function testSetGetConstructor() + public function testSetGetFactoryClass() + { + $def = new Definition('stdClass'); + $this->assertNull($def->getFactoryClass()); + $this->assertSame($def, $def->setFactoryClass('stdClass2'), "->setFactoryClass() implements a fluent interface."); + $this->assertEquals('stdClass2', $def->getFactoryClass(), "->getFactoryClass() returns current class to construct this service."); + } + + public function testSetGetFactoryMethod() { $def = new Definition('stdClass'); + $this->assertNull($def->getFactoryMethod()); $this->assertSame($def, $def->setFactoryMethod('foo'), '->setFactoryMethod() implements a fluent interface'); $this->assertEquals('foo', $def->getFactoryMethod(), '->getFactoryMethod() returns the factory method name'); } @@ -42,8 +47,8 @@ public function testSetGetFactoryService() { $def = new Definition('stdClass'); $this->assertNull($def->getFactoryService()); - $this->assertSame($def, $def->setFactoryService('stdClass2'), "->setFactoryService() implements a fluent interface."); - $this->assertEquals('stdClass2', $def->getFactoryService(), "->getFactoryService() returns current service to construct this service."); + $this->assertSame($def, $def->setFactoryService('foo.bar'), "->setFactoryService() implements a fluent interface."); + $this->assertEquals('foo.bar', $def->getFactoryService(), "->getFactoryService() returns current service to construct this service."); } /** diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php index 46c9dbb90932f..03e4c9db92829 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php @@ -12,6 +12,7 @@ register('foo', 'FooClass')-> addTag('foo', array('foo' => 'foo'))-> addTag('foo', array('bar' => 'bar'))-> + setFactoryClass('FooClass')-> setFactoryMethod('getInstance')-> setArguments(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, new Reference('service_container')))-> setScope('prototype')-> @@ -27,6 +28,7 @@ ; $container-> register('foo.baz', '%baz_class%')-> + setFactoryClass('%baz_class%')-> setFactoryMethod('getInstance')-> setConfigurator(array('%baz_class%', 'configureStatic1')) ; diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php index c3efeb9f47207..27503a351c674 100755 --- a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php @@ -12,12 +12,14 @@ return $container; -class FooClass -{ - public $bar; - - public function setBar($bar) +if (!class_exists('FooClass')) { + class FooClass { - $this->bar = $bar; + public $bar; + + public function setBar($bar) + { + $this->bar = $bar; + } } -} \ No newline at end of file +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php index 15048f7c3dd84..7f5e41a7be800 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php @@ -28,10 +28,16 @@ public function testGetPathReturnsAbsolutePath() $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', $this->file->getPath()); } + public function test__toString() + { + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', (string) $this->file); + } + public function testGetWebPathReturnsPathRelativeToDocumentRoot() { File::setDocumentRoot(__DIR__); + $this->assertEquals(__DIR__, File::getDocumentRoot()); $this->assertEquals('/Fixtures/test.gif', $this->file->getWebPath()); } @@ -42,11 +48,24 @@ public function testGetWebPathReturnsEmptyPathIfOutsideDocumentRoot() $this->assertEquals('', $this->file->getWebPath()); } + public function testSetDocumentRootThrowsLogicExceptionWhenNotExists() + { + $this->setExpectedException('LogicException'); + + File::setDocumentRoot(__DIR__.'/Fixtures/not_here'); + } + public function testGetNameReturnsNameWithExtension() { $this->assertEquals('test.gif', $this->file->getName()); } + public function testGetExtensionReturnsEmptyString() + { + $file = new File(__DIR__.'/Fixtures/test'); + $this->assertEquals('', $file->getExtension()); + } + public function testGetExtensionReturnsExtensionWithDot() { $this->assertEquals('.gif', $this->file->getExtension()); @@ -66,6 +85,13 @@ public function testGetMimeTypeUsesMimeTypeGuessers() $this->assertEquals('image/gif', $this->file->getMimeType()); } + public function testGetDefaultExtensionWithoutGuesser() + { + $file = new File(__DIR__.'/Fixtures/directory/.empty'); + + $this->assertEquals('.empty', $file->getDefaultExtension()); + } + public function testGetDefaultExtensionIsBasedOnMimeType() { $file = new File(__DIR__.'/Fixtures/test'); @@ -76,11 +102,33 @@ public function testGetDefaultExtensionIsBasedOnMimeType() $this->assertEquals('.gif', $file->getDefaultExtension()); } + public function testConstructWhenFileNotExists() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new File(__DIR__.'/Fixtures/not_here'); + } + public function testSizeReturnsFileSize() { $this->assertEquals(filesize($this->file->getPath()), $this->file->size()); } + public function testSizeFailing() + { + $dir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory'; + $path = $dir.DIRECTORY_SEPARATOR.'test.copy.gif'; + @unlink($path); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + @unlink($path); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileException'); + $file->size($path); + + } + public function testMove() { $path = __DIR__.'/Fixtures/test.copy.gif'; @@ -121,6 +169,27 @@ public function testMoveWithNewName() @unlink($targetPath); } + public function testMoveFailing() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetPath = '/thisfolderwontexist'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileException'); + $file->move($targetPath); + + $this->assertFileExists($path); + $this->assertFileNotExists($path.$targetPath.'test.gif'); + $this->assertEquals($path, $file->getPath()); + + @unlink($path); + @unlink($targetPath); + } + public function testRename() { $path = __DIR__.'/Fixtures/test.copy.gif'; diff --git a/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php b/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php deleted file mode 100644 index b3bf6fed30e6f..0000000000000 --- a/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Tests\Component\HttpKernel\Bundle; - -class BundleTest extends \PHPUnit_Framework_TestCase -{ - public function testGetNormalizedPathReturnsANormalizedPath() - { - $bundle = $this - ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') - ->setMethods(array('getPath')) - ->disableOriginalConstructor() - ->getMockForAbstractClass() - ; - - $bundle - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('path\\to\\foo\\bar')) - ; - - $this->assertEquals('path/to/foo/bar', $bundle->getNormalizedPath()); - } -} diff --git a/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 0000000000000..4b75a4717392f --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerAggregateTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 0000000000000..063a114978da0 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertTrue(file_exists(self::$cacheFile)); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index 5695f5a6fdbaf..a97eb74aeb926 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -255,7 +255,7 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu { $bundle = $this ->getMockBuilder('Symfony\Tests\Component\HttpKernel\BundleForTest') - ->setMethods(array('getNormalizedPath', 'getParent', 'getName')) + ->setMethods(array('getPath', 'getParent', 'getName')) ->disableOriginalConstructor() ; @@ -273,7 +273,7 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu $bundle ->expects($this->any()) - ->method('getNormalizedPath') + ->method('getPath') ->will($this->returnValue(strtr($dir, '\\', '/'))) ;