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/DoctrineAbstractBundle/Common/DataFixtures/Loader.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php new file mode 100644 index 0000000000000..1e7c2f754b2f5 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php @@ -0,0 +1,27 @@ +container = $container; + } + + public function addFixture(FixtureInterface $fixture) + { + if ($fixture instanceof ContainerAwareInterface) { + $fixture->setContainer($this->container); + } + + parent::addFixture($fixture); + } +} diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php new file mode 100644 index 0000000000000..51a873cd56dac --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php @@ -0,0 +1,21 @@ +container = $container; + } + + public function load($manager) + { + } +} diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php new file mode 100644 index 0000000000000..8bef32a6e230c --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php @@ -0,0 +1,21 @@ +getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $loader = new Loader($container); + $fixture = new ContainerAwareFixture(); + + $loader->addFixture($fixture); + + $this->assertSame($container, $fixture->container); + } +} diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php new file mode 100644 index 0000000000000..17326b6d5a876 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php @@ -0,0 +1,13 @@ +markTestSkipped('Doctrine Data Fixtures is not available.'); + } + } +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php index f0557b34eff04..3a369bbd61ef7 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php +++ b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php @@ -18,8 +18,11 @@ use Symfony\Component\Console\Output\Output; use Symfony\Component\Finder\Finder; use Symfony\Bundle\FrameworkBundle\Util\Filesystem; +use Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader as DataFixturesLoader; use Doctrine\Common\Cli\Configuration; use Doctrine\Common\Cli\CliController as DoctrineCliController; +use Doctrine\Common\DataFixtures\Executor\ORMExecutor; +use Doctrine\Common\DataFixtures\Purger\ORMPurger; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Mapping\ClassMetadata; @@ -72,15 +75,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $loader = new \Doctrine\Common\DataFixtures\Loader(); + $loader = new DataFixturesLoader($this->container); foreach ($paths as $path) { if (is_dir($path)) { $loader->loadFromDirectory($path); } } $fixtures = $loader->getFixtures(); - $purger = new \Doctrine\Common\DataFixtures\Purger\ORMPurger($em); - $executor = new \Doctrine\Common\DataFixtures\Executor\ORMExecutor($em, $purger); + $purger = new ORMPurger($em); + $executor = new ORMExecutor($em, $purger); $executor->setLogger(function($message) use ($output) { $output->writeln(sprintf(' > %s', $message)); }); 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/Command/LoadDataFixturesDoctrineODMCommand.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php index 51609ab4d3771..bcaf9e0d85a16 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php @@ -18,8 +18,11 @@ use Symfony\Component\Console\Output\Output; use Symfony\Component\Finder\Finder; use Symfony\Bundle\FrameworkBundle\Util\Filesystem; +use Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader as DataFixturesLoader; use Doctrine\Common\Cli\Configuration; use Doctrine\Common\Cli\CliController as DoctrineCliController; +use Doctrine\Common\DataFixtures\Executor\MongoDBExecutor; +use Doctrine\Common\DataFixtures\Purger\MongoDBPurger; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Internal\CommitOrderCalculator; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -72,15 +75,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $paths = array_filter($paths, 'is_dir'); - - $loader = new \Doctrine\Common\DataFixtures\Loader(); + $loader = new DataFixturesLoader($this->container); foreach ($paths as $path) { - $loader->loadFromDirectory($path); + if (is_dir($path)) { + $loader->loadFromDirectory($path); + } } $fixtures = $loader->getFixtures(); - $purger = new \Doctrine\Common\DataFixtures\Purger\MongoDBPurger($dm); - $executor = new \Doctrine\Common\DataFixtures\Executor\MongoDBExecutor($dm, $purger); + $purger = new MongoDBPurger($dm); + $executor = new MongoDBExecutor($dm, $purger); $executor->setLogger(function($message) use ($output) { $output->writeln(sprintf(' > %s', $message)); }); 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/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php index 4af23affe6d8c..f3e0d6fae7e1c 100755 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -5,10 +5,11 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\DocumentRepository; use Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator\Document; +use Symfony\Bundle\DoctrineMongoDBBundle\Tests\TestCase; use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\Unique; use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\UniqueValidator; -class UniqueValidatorTest extends \PHPUnit_Framework_TestCase +class UniqueValidatorTest extends TestCase { private $dm; private $repository; @@ -18,6 +19,7 @@ class UniqueValidatorTest extends \PHPUnit_Framework_TestCase public function setUp() { + parent::setUp(); $this->classMetadata = $this->getClassMetadata(); $this->repository = $this->getDocumentRepository(); $this->dm = $this->getDocumentManager($this->classMetadata, $this->repository); 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/Command/InitBundleCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php index deb6a1639befb..500eb99eda21c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php @@ -71,16 +71,18 @@ protected function execute(InputInterface $input, OutputInterface $output) // user specified bundle name? $bundle = $input->getArgument('bundleName'); - if ('' === $bundle) { + if (!$bundle) { $bundle = strtr($namespace, array('\\' => '')); - } elseif (!preg_match('/Bundle$/', $bundle)) { + } + + if (!preg_match('/Bundle$/', $bundle)) { throw new \InvalidArgumentException('The bundle name must end with Bundle.'); } $dir = $input->getArgument('dir'); // add trailing / if necessary - $dir = '/' === substr($dir, -1, 1) ? $dir : $dir . '/'; + $dir = '/' === substr($dir, -1, 1) ? $dir : $dir.'/'; $targetDir = $dir.strtr($namespace, '\\', '/'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 118bbfaeb6696..806bd1835ef37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -34,9 +34,11 @@ public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; - parent::__construct('Symfony', Kernel::VERSION.' - '.$kernel->getName()); + parent::__construct('Symfony', Kernel::VERSION.' - '.$kernel->getName().'/'.$kernel->getEnvironment().($kernel->isDebug() ? '/debug' : '')); $this->definition->addOption(new InputOption('--shell', '-s', InputOption::VALUE_NONE, 'Launch the shell.')); + $this->definition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev')); + $this->definition->addOption(new InputOption('--debug', '-d', InputOption::VALUE_NONE, 'Whether to run in debug mode.')); $this->kernel->boot(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index cb094a3d6d37c..0f95d187a1a31 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -47,7 +47,7 @@ public function redirectAction($route, $permanent = false) $code = $permanent ? 301 : 302; $attributes = $this->container->get('request')->attributes->all(); - unset($attributes['_route'], $attributes['route']); + unset($attributes['_route'], $attributes['route'], $attributes['permanent'] ); $response = $this->container->get('response'); $response->setRedirect($this->container->get('router')->generate($route, $attributes), $code); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..066d1f3c829a9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -0,0 +1,222 @@ + + */ +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('exception_controller')->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() + ->scalarNode('type')->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..d6121b0fc00dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -11,150 +11,122 @@ 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['exception_controller'])) { + $container->setParameter('exception_listener.controller', $config['exception_controller']); } - 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['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); } - if (isset($config['router'])) { - $this->registerRouterConfiguration($config, $container); + if (isset($config['test']) && $config['test']) { + $loader->load('test.xml'); + $config['session']['storage_id'] = 'array'; } - if (isset($config['profiler'])) { - $this->registerProfilerConfiguration($config, $container); + if (isset($config['csrf_protection'])) { + $this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container); } - if (isset($config['validation']['enabled'])) { - $this->registerValidationConfiguration($config, $container); + if (isset($config['esi'])) { + $this->registerEsiConfiguration($config['esi'], $loader); } - if (array_key_exists('templating', $config)) { - $this->registerTemplatingConfiguration($config, $container); + if (isset($config['profiler'])) { + $this->registerProfilerConfiguration($config['profiler'], $container, $loader); } - if (array_key_exists('test', $config)) { - $this->registerTestConfiguration($config, $container); + if (isset($config['router'])) { + $this->registerRouterConfiguration($config['router'], $container, $loader); } - if (array_key_exists('session', $config)) { - $this->registerSessionConfiguration($config, $container); + if (isset($config['session'])) { + $this->registerSessionConfiguration($config['session'], $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['templating'])) { + $this->registerTemplatingConfiguration($config['templating'], $container, $loader); } - if (isset($config['cache-warmer'])) { - $config['cache_warmer'] = $config['cache-warmer']; + if (isset($config['translator'])) { + $this->registerTranslatorConfiguration($config['translator'], $container); } - $warmer = isset($config['cache_warmer']) ? $config['cache_warmer'] : !$container->getParameter('kernel.debug'); - $container->setParameter('kernel.cache_warmup', $warmer); + if (isset($config['validation'])) { + $this->registerValidationConfiguration($config['validation'], $container, $loader); + } $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\ParameterBag', @@ -178,236 +150,119 @@ 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['type'])) { + $container->setParameter('router.options.resource_type', $config['type']); } - if (isset($config['auto-start'])) { - $config['auto_start'] = $config['auto-start']; + if (isset($config['cache_warmer']) && $config['cache_warmer']) { + $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); + $container->setAlias('router', 'router.cached'); } + $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 +271,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 +293,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']) && $config['loaders']) { + $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']); + + // Use a delegation 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/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 65ff4e29b029c..c75ca9aa9df1a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -22,6 +22,7 @@ Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer %kernel.name%%kernel.environment%UrlMatcher %kernel.name%%kernel.environment%UrlGenerator + @@ -71,6 +72,7 @@ %router.options.matcher_base_class% %router.options.matcher_dumper_class% %router.options.matcher.cache_class% + %router.options.resource_type% 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..4a635a758e83a 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,51 @@ - - + + + - - + - + - + + - + + + + + + + + + + + + + + + + + @@ -46,14 +63,9 @@ + - - - - - - @@ -64,11 +76,14 @@ - - + + + + + @@ -83,21 +98,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/Resources/translations/validators.es.xliff b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff new file mode 100644 index 0000000000000..96e2fc6063498 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff @@ -0,0 +1,131 @@ + + + + + + This value should be false + Este valor debería ser falso + + + This value should be true + Este valor debería ser verdadero + + + This value should be of type {{ type }} + Este valor debería ser de tipo {{ type }} + + + This value should be blank + Este valor debería estar vacío + + + This value should be one of the given choices + Seleccione este valor entre las opciones mostradas + + + You should select at least {{ limit }} choices + Debería seleccionar al menos {{ limit }} opciones + + + You should select at most {{ limit }} choices + Debería seleccionar como máximo {{ limit }} opciones + + + The fields {{ fields }} were not expected + No se esperaban los campos {{ fields }} + + + The fields {{ fields }} are missing + Faltan los campos {{ fields }} + + + This value is not a valid date + Este valor no es una fecha válida + + + This value is not a valid datetime + Este valor no es una fecha y hora válidas + + + This value is not a valid email address + Este valor no es una dirección de email válida + + + The file could not be found + No se pudo encontrar el archivo + + + The file is not readable + No se puede leer el archivo + + + The file is too large ({{ size }}). Allowed maximum size is {{ limit }} + El archivo es demasiado grande ({{ size }}). El tamaño máximo permitido es {{ limit }} + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} + El tipo mime del archivo no es válido ({{ type }}). Los tipos mime válidos son {{ types }} + + + This value should be {{ limit }} or less + Este valor debería ser {{ limit }} o menos + + + This value is too long. It should have {{ limit }} characters or less + Este valor es demasiado largo. Debería tener {{ limit }} caracteres o menos + + + This value should be {{ limit }} or more + Este valor debería ser {{ limit }} o más + + + This value is too short. It should have {{ limit }} characters or more + Este valor es demasiado corto. Debería tener {{ limit }} caracteres o más + + + This value should not be blank + Este valor no debería estar vacío + + + This value should not be null + Este valor no debería ser null + + + This value should be null + Este valor debería ser null + + + This value is not valid + Este valor no es válido + + + This value is not a valid time + Este valor no es una hora válida + + + This value is not a valid URL + Este valor no es una URL válida + + + This value should be instance of class {{ class }} + Este valor debería ser una instancia de la clase {{ class }} + + + This field group should not contain extra fields + Este grupo de campos no debería contener campos adicionales + + + The uploaded file was too large. Please try to upload a smaller file + El archivo subido es demasiado grande. Por favor, suba un archivo más pequeño + + + The CSRF token is invalid + El token CSRF no es válido + + + The two values should be equal + Los dos valores deberían ser iguales + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff new file mode 100644 index 0000000000000..cabba0c0d2f69 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff @@ -0,0 +1,131 @@ + + + + + + This value should be false + 値はfalseでなければなりません + + + This value should be true + 値はtrueでなければなりません + + + This value should be of type {{ type }} + 値の型は{{ type }}でなければなりません + + + This value should be blank + 値は空でなければなりません + + + This value should be one of the given choices + 値は指定された選択肢のうちの1つでなければなりません + + + You should select at least {{ limit }} choices + {{ limit }}個以上選択してください + + + You should select at most {{ limit }} choices + {{ limit }}個以内で選択してください + + + The fields {{ fields }} were not expected + フィールド{{ fields }}は無効です + + + The fields {{ fields }} are missing + フィールド{{ fields }}は必須です + + + This value is not a valid date + 値が有効な日付ではありません + + + This value is not a valid datetime + 値が有効な日時ではありません + + + This value is not a valid email address + 値が有効なメールアドレスではありません + + + The file could not be found + ファイルが見つかりません + + + The file is not readable + ファイルを読み込めません + + + The file is too large ({{ size }}). Allowed maximum size is {{ limit }} + ファイルのサイズが大きすぎます({{ size }})。有効な最大サイズは{{ limit }}です + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} + ファイルのMIMEタイプが無効です({{ type }})。有効なMIMEタイプは{{ types }}です + + + This value should be {{ limit }} or less + 値は{{ limit }}以下でなければなりません + + + This value is too long. It should have {{ limit }} characters or less + 値が長すぎます。{{ limit }}文字以内でなければなりません + + + This value should be {{ limit }} or more + 値は{{ limit }}以上でなければなりません + + + This value is too short. It should have {{ limit }} characters or more + 値が短すぎます。{{ limit }}文字以上でなければなりません + + + This value should not be blank + 値が空であってはなりません + + + This value should not be null + 値がnullであってはなりません + + + This value should be null + 値はnullでなければなりません + + + This value is not valid + 値が無効です + + + This value is not a valid time + 値が有効な時刻ではありません + + + This value is not a valid URL + 値が有効なURLではありません + + + This value should be instance of class {{ class }} + 値は{{ class }}のインスタンスでなければなりません + + + This field group should not contain extra fields + フィールドグループに追加のフィールドを含んではなりません + + + The uploaded file was too large. Please try to upload a smaller file + アップロードされたファイルが大きすぎます。小さなファイルで再度アップロードしてください + + + The CSRF token is invalid + CSRFトークンが無効です + + + The two values should be equal + 2つの値が同じでなければなりません + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.html.php similarity index 100% rename from src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.php rename to src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.html.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php index 438d32ee18d1a..8885834462922 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php @@ -69,7 +69,7 @@ protected function getEngine($name) } } - throw new \RuntimeException(sprintf('No engine is able to work with the "%s" template.', $name)); + throw new \RuntimeException(sprintf('No engine is able to work with the %s template.', json_encode($name))); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php index ff762c31070d0..e6ecf2a3cafe6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php @@ -53,12 +53,12 @@ public function parse($name) $parts = explode(':', $name); if (3 !== count($parts)) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name)); + throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); } $elements = explode('.', $parts[2]); if (3 !== count($elements)) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name)); + throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); } $parameters = array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php new file mode 100644 index 0000000000000..5f0d3a06a8d13 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; +use Symfony\Bundle\FrameworkBundle\Tests\Logger; +use Symfony\Bundle\FrameworkBundle\Tests\Kernel; + + + +/** + * + * @author Marcin Sikon + */ +class RedirectControllerTest extends TestCase +{ + public function testEmptyRoute() + { + $response = new Response(); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('get') + ->with($this->equalTo('response')) + ->will($this->returnValue($response)) + ; + + $controller = new RedirectController(); + $controller->setContainer($container); + + $returnResponse = $controller->redirectAction(''); + + $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); + + $this->assertEquals(410, $returnResponse->getStatusCode()); + } + + + + /** + * @dataProvider provider + */ + public function testRoute($permanent, $expectedCode) + { + $response = new Response(); + $request = new Request(); + + $route = 'new-route'; + $url = '/redirect-url'; + $params = array('additional-parameter' => 'value'); + + + $request->attributes = new ParameterBag(array('route' => $route, '_route' => 'current-route', 'permanent' => $permanent) + $params); + + $router = $this->getMock('Symfony\Component\Routing\RouterInterface'); + $router + ->expects($this->once()) + ->method('generate') + ->with($this->equalTo($route),$this->equalTo($params)) + ->will($this->returnValue($url)); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + + $container + ->expects($this->at(0)) + ->method('get') + ->with($this->equalTo('request')) + ->will($this->returnValue($request)); + + $container + ->expects($this->at(1)) + ->method('get') + ->with($this->equalTo('response')) + ->will($this->returnValue($response)); + + $container + ->expects($this->at(2)) + ->method('get') + ->with($this->equalTo('router')) + ->will($this->returnValue($router)); + + + $controller = new RedirectController(); + $controller->setContainer($container); + + $returnResponse = $controller->redirectAction($route, $permanent); + + + $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); + + $this->assertTrue($returnResponse->isRedirect()); + $this->assertTrue($returnResponse->isRedirected($url)); + $this->assertEquals($expectedCode, $returnResponse->getStatusCode()); + } + + + + public function provider() + { + return array( + array(true, 301), + array(false, 302), + ); + } + +} 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..7439b4dc9f972 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -0,0 +1,46 @@ +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( + 'cache_warmer' => true, + 'resource' => '%kernel.root_dir%/config/routing.xml', + 'type' => 'xml', + ), + '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..8c508d98e2c38 --- /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..74571b6e6d479 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -0,0 +1,35 @@ +app.config: + csrf_protection: + enabled: true + field_name: _csrf + secret: s3cr3t + esi: + enabled: true + profiler: + only_exceptions: true + router: + cache_warmer: true + resource: %kernel.root_dir%/config/routing.xml + type: xml + 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..95d7d21decfc8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -16,43 +16,183 @@ 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->assertEquals('xml', $container->getParameter('router.options.resource_type'), '->registerRouterConfiguration() sets routing resource type'); + $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..2bb11266c2901 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php @@ -0,0 +1,235 @@ + + */ +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') + ->performNoDeepMerging() + ->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/Extension/FormExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php index aab5c28c556ee..4f96e842111bf 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php @@ -123,12 +123,17 @@ public function renderRow(FieldInterface $field) * * {{ form_field(field, {}, {'separator': '+++++'}) }} * - * @param FieldInterface $field The field to render - * @param array $params Additional variables passed to the template - * @param string $resources + * @param FieldInterface $field The field to render + * @param array $attributes HTML attributes passed to the template + * @param array $parameters Additional variables passed to the template + * @param array|string $resources A resource or array of resources */ public function renderField(FieldInterface $field, array $attributes = array(), array $parameters = array(), $resources = null) { + if (null !== $resources && !is_array($resources)) { + $resources = array($resources); + } + return $this->render($field, 'field', array( 'field' => $field, 'attr' => $attributes, @@ -190,7 +195,7 @@ public function renderData(FieldInterface $field) return $field->getData(); } - protected function render(FieldInterface $field, $name, array $arguments, $resources = null) + protected function render(FieldInterface $field, $name, array $arguments, array $resources = null) { if ('field' === $name) { list($name, $template) = $this->getWidget($field, $resources); 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/TwigBundle/TwigEngine.php b/src/Symfony/Bundle/TwigBundle/TwigEngine.php index 20d7082785c3e..7fdf2d77c2a38 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigEngine.php +++ b/src/Symfony/Bundle/TwigBundle/TwigEngine.php @@ -131,6 +131,6 @@ protected function load($name) return $name; } - return $this->environment->loadTemplate($this->parser->parse($name), is_array($name) ? json_encode($name) : $name); + return $this->environment->loadTemplate($this->parser->parse($name)); } } 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/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 02c11dc47968f..dffb1e9327935 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -266,16 +266,39 @@ protected function getScript($request) // @codeCoverageIgnoreEnd } + /** + * Filters the request. + * + * @param Request $request The request to filter + * + * @return Request + */ protected function filterRequest(Request $request) { return $request; } + /** + * Filters the Response. + * + * @param Response $response The Response to filter + * + * @return Response + */ protected function filterResponse($response) { return $response; } + /** + * Creates a crawler. + * + * @param string $uri A uri + * @param string $content Content for the crawler to use + * @param string $type Content type + * + * @return Crawler + */ protected function createCrawlerFromContent($uri, $content, $type) { $crawler = new Crawler(null, $uri); @@ -286,6 +309,8 @@ protected function createCrawlerFromContent($uri, $content, $type) /** * Goes back in the browser history. + * + * @return Crawler */ public function back() { @@ -294,6 +319,8 @@ public function back() /** * Goes forward in the browser history. + * + * @return Crawler */ public function forward() { @@ -302,6 +329,8 @@ public function forward() /** * Reloads the current browser. + * + * @return Crawler */ public function reload() { @@ -335,6 +364,12 @@ public function restart() $this->history->clear(); } + /** + * Takes a URI and converts it to absolute if it is not already absolute. + * + * @param string $uri A uri + * @return string An absolute uri + */ protected function getAbsoluteUri($uri) { // already absolute? diff --git a/src/Symfony/Component/BrowserKit/Response.php b/src/Symfony/Component/BrowserKit/Response.php index 0ed9a4c064b66..a80129a802ae2 100644 --- a/src/Symfony/Component/BrowserKit/Response.php +++ b/src/Symfony/Component/BrowserKit/Response.php @@ -39,6 +39,11 @@ public function __construct($content = '', $status = 200, array $headers = array $this->headers = $headers; } + /** + * Converts the response object to string containing all headers and the response content. + * + * @return string The response with headers and content + */ public function __toString() { $headers = ''; diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index 62e9f4f5dffc3..d5419a06ce769 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -119,6 +119,10 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = /** * Adds brackets around each namespace if it's not already the case. + * + * @param string $source Namespace string + * + * @return string Namespaces with brackets */ static public function fixNamespaceDeclarations($source) { @@ -162,6 +166,15 @@ static public function fixNamespaceDeclarations($source) return $output; } + /** + * Writes a cache file. + * + * @param string $file Filename + * @param string $content Temporary file content + * + * @throws \RuntimeException when a cache file cannot be written + */ + static protected function writeCacheFile($file, $content) { $tmpFile = tempnam(dirname($file), basename($file)); diff --git a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php index 8a370af645a85..0a4d5c15e64b7 100644 --- a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php +++ b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ClassLoader; /** + * A class loader that uses a mapping file to look up paths. * * @author Fabien Potencier */ @@ -19,6 +20,11 @@ class MapFileClassLoader { protected $map = array(); + /** + * Constructor. + * + * @param string $file Path to class mapping file + */ public function __construct($file) { $this->map = require $file; diff --git a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php index 55d3527d57f79..0394fa496e294 100644 --- a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php +++ b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php @@ -102,7 +102,7 @@ public function getPrefixFallback() /** * Registers the directory to use as a fallback for namespaces. * - * @return string|array $dirs A directory path or an array of directories + * @param string|array $dirs A directory path or an array of directories */ public function registerNamespaceFallback($dirs) { @@ -112,7 +112,7 @@ public function registerNamespaceFallback($dirs) /** * Registers the directory to use as a fallback for class prefixes. * - * @return string|array $dirs A directory path or an array of directories + * @param string|array $dirs A directory path or an array of directories */ public function registerPrefixFallback($dirs) { diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 89c31bade7fa0..d7509de85b245 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -201,7 +201,7 @@ public function setHelperSet(HelperSet $helperSet) } /** - * Get the helper set associated with the command + * Get the helper set associated with the command. * * @return HelperSet The HelperSet instance associated with this command */ @@ -396,7 +396,7 @@ public function get($name) } /** - * Returns true if the command exists, false otherwise + * Returns true if the command exists, false otherwise. * * @param string $name The command name or alias * @@ -429,6 +429,8 @@ public function getNamespaces() /** * Finds a registered namespace by a name or an abbreviation. * + * @param string $namespace A namespace or abbreviation to search for + * * @return string A registered namespace * * @throws \InvalidArgumentException When namespace is incorrect or ambiguous @@ -715,11 +717,25 @@ public function renderException($e, $output) } } + /** + * Gets the name of the command based on input. + * + * @param InputInterface $input The input interface + * + * @return string The command name + */ protected function getCommandName(InputInterface $input) { return $input->getFirstArgument('command'); } + /** + * Sorts commands in alphabetical order. + * + * @param array $commands An associative array of commands to sort + * + * @return array A sorted array of commands + */ protected function sortCommands($commands) { $namespacedCommands = array(); @@ -741,6 +757,13 @@ protected function sortCommands($commands) return $namespacedCommands; } + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ protected function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php index 49e91782dcde0..61ff032c0a383 100644 --- a/src/Symfony/Component/Console/Command/HelpCommand.php +++ b/src/Symfony/Component/Console/Command/HelpCommand.php @@ -28,7 +28,7 @@ class HelpCommand extends Command protected $command; /** - * @see Command + * {@inheritdoc} */ protected function configure() { @@ -54,13 +54,18 @@ protected function configure() ); } + /** + * Sets the command + * + * @param Command $command The command to set + */ public function setCommand(Command $command) { $this->command = $command; } /** - * @see Command + * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { diff --git a/src/Symfony/Component/Console/Command/ListCommand.php b/src/Symfony/Component/Console/Command/ListCommand.php index 3d2ee7bb159f8..76fb7cbcefdba 100644 --- a/src/Symfony/Component/Console/Command/ListCommand.php +++ b/src/Symfony/Component/Console/Command/ListCommand.php @@ -26,7 +26,7 @@ class ListCommand extends Command { /** - * @see Command + * {@inheritdoc} */ protected function configure() { @@ -54,7 +54,7 @@ protected function configure() } /** - * @see Command + * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { diff --git a/src/Symfony/Component/Console/Helper/FormatterHelper.php b/src/Symfony/Component/Console/Helper/FormatterHelper.php index 7da190631fad9..5a05aa049a994 100644 --- a/src/Symfony/Component/Console/Helper/FormatterHelper.php +++ b/src/Symfony/Component/Console/Helper/FormatterHelper.php @@ -67,6 +67,13 @@ public function formatBlock($messages, $style, $large = false) return implode("\n", $messages); } + /** + * Returns the length of a string, uses mb_strlen if it is available. + * + * @param string $string The string to check its length + * + * @return integer The length of the string + */ protected function strlen($string) { return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string); @@ -74,6 +81,8 @@ protected function strlen($string) /** * Returns the helper's canonical name + * + * @return string The canonical name of the helper */ public function getName() { diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 5662e5f69bdcb..6ad48121442aa 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -252,4 +252,36 @@ public function hasParameterOption($values) return false; } + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + if (!is_array($values)) { + $values = array($values); + } + + $tokens = $this->tokens; + while ($token = array_shift($tokens)) { + foreach ($values as $value) { + if (0 === strpos($token, $value)) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } else { + return array_shift($this->tokens); + } + } + } + } + + return $default; + } } diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index e9572d71ed297..56c5fad9ab518 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -82,6 +82,34 @@ public function hasParameterOption($values) return false; } + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + if (!is_array($values)) { + $values = array($values); + } + + foreach ($this->parameters as $k => $v) { + if (is_int($k) && in_array($v, $values)) { + return true; + } elseif (in_array($k, $values)) { + return $v; + } + } + + return $default; + } + /** * Processes command line arguments. */ diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index 8fcf9899251f6..aba45273a7bc9 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -64,6 +64,8 @@ public function bind(InputDefinition $definition) abstract protected function parse(); /** + * Validates the input. + * * @throws \RuntimeException When not enough arguments are given */ public function validate() @@ -73,11 +75,21 @@ public function validate() } } + /** + * Checks if the input is interactive. + * + * @return Boolean Returns true if the input is interactive + */ public function isInteractive() { return $this->interactive; } + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ public function setInteractive($interactive) { $this->interactive = (Boolean) $interactive; diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php index a4ed4ae3a2a9b..9a7baff2a60e5 100644 --- a/src/Symfony/Component/Console/Input/InputDefinition.php +++ b/src/Symfony/Component/Console/Input/InputDefinition.php @@ -42,6 +42,11 @@ public function __construct(array $definition = array()) $this->setDefinition($definition); } + /** + * Sets the definition of the input. + * + * @param array $definition The definition array + */ public function setDefinition(array $definition) { $arguments = array(); @@ -297,6 +302,8 @@ public function hasShortcut($name) /** * Gets an InputOption by shortcut. * + * @param string $shortcut the Shortcut name + * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index bf943716a4e1a..53147732987ab 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -31,11 +31,24 @@ function getFirstArgument(); * This method is to be used to introspect the input parameters * before it has been validated. It must be used carefully. * - * @param string $value The value to look for in the raw parameters + * @param string|array $value The values to look for in the raw parameters (can be an array) * * @return Boolean true if the value is contained in the raw parameters */ - function hasParameterOption($value); + function hasParameterOption($values); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + function getParameterOption($values, $default = false); /** * Binds the current Input instance with the given arguments and options. @@ -63,6 +76,7 @@ function getArguments(); /** * Get argument by name. * + * @param string $name The name of the argument * @return mixed */ function getArgument($name); @@ -72,6 +86,12 @@ function getArgument($name); */ function getOptions(); + /** + * Get an option by name. + * + * @param string $name The name of the option + * @return mixed + */ function getOption($name); /** diff --git a/src/Symfony/Component/Console/Input/StringInput.php b/src/Symfony/Component/Console/Input/StringInput.php index 64d9772af2d99..7a7575996b9c8 100644 --- a/src/Symfony/Component/Console/Input/StringInput.php +++ b/src/Symfony/Component/Console/Input/StringInput.php @@ -39,6 +39,9 @@ public function __construct($input, InputDefinition $definition = null) } /** + * Tokenizes a string. + * + * @param string $input The input to tokenise * @throws \InvalidArgumentException When unable to parse input (should never happen) */ protected function tokenize($input) diff --git a/src/Symfony/Component/Console/Output/Output.php b/src/Symfony/Component/Console/Output/Output.php index 388848614af0c..acc1a2f03c9b2 100644 --- a/src/Symfony/Component/Console/Output/Output.php +++ b/src/Symfony/Component/Console/Output/Output.php @@ -179,6 +179,12 @@ protected function format($message) } /** + * Replaces the starting style of the output. + * + * @param array $match + * + * @return string The replaced style + * * @throws \InvalidArgumentException When style is unknown */ protected function replaceStartStyle($match) @@ -220,6 +226,13 @@ protected function replaceStartStyle($match) return "\033[".implode(';', $codes).'m'; } + /** + * Replaces the end style. + * + * @param string $match The text to match + * + * @return string The end style + */ protected function replaceEndStyle($match) { if (!$this->decorated) { diff --git a/src/Symfony/Component/CssSelector/Node/AttribNode.php b/src/Symfony/Component/CssSelector/Node/AttribNode.php index 30128826cdb28..3471986d2d166 100644 --- a/src/Symfony/Component/CssSelector/Node/AttribNode.php +++ b/src/Symfony/Component/CssSelector/Node/AttribNode.php @@ -30,6 +30,15 @@ class AttribNode implements NodeInterface protected $operator; protected $value; + /** + * Constructor. + * + * @param NodeInterface $selector The XPath selector + * @param string $namespace The namespace + * @param string $attrib The attribute + * @param string $operator The operator + * @param string $value The value + */ public function __construct($selector, $namespace, $attrib, $operator, $value) { $this->selector = $selector; @@ -39,6 +48,9 @@ public function __construct($selector, $namespace, $attrib, $operator, $value) $this->value = $value; } + /** + * {@inheritDoc} + */ public function __toString() { if ($this->operator == 'exists') { @@ -49,7 +61,7 @@ public function __toString() } /** - * @throws SyntaxError When unknown operator is found + * {@inheritDoc} */ public function toXpath() { @@ -88,6 +100,11 @@ public function toXpath() return $path; } + /** + * Returns the XPath Attribute + * + * @return string The XPath attribute + */ protected function xpathAttrib() { // FIXME: if attrib is *? @@ -98,6 +115,11 @@ protected function xpathAttrib() return sprintf('@%s:%s', $this->namespace, $this->attrib); } + /** + * Returns a formatted attribute + * + * @return string The formatted attributep + */ protected function formatAttrib() { if ($this->namespace == '*') { diff --git a/src/Symfony/Component/CssSelector/Node/ClassNode.php b/src/Symfony/Component/CssSelector/Node/ClassNode.php index de7e931c4fe57..850b44ec892ea 100644 --- a/src/Symfony/Component/CssSelector/Node/ClassNode.php +++ b/src/Symfony/Component/CssSelector/Node/ClassNode.php @@ -26,17 +26,29 @@ class ClassNode implements NodeInterface protected $selector; protected $className; + /** + * The constructor. + * + * @param NodeInterface $selector The XPath Selector + * @param string $className The class name + */ public function __construct($selector, $className) { $this->selector = $selector; $this->className = $className; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s.%s]', __CLASS__, $this->selector, $this->className); } + /** + * {@inheritDoc} + */ public function toXpath() { $selXpath = $this->selector->toXpath(); diff --git a/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php b/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php index 1662d59f58433..80e88e130a51f 100644 --- a/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php +++ b/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php @@ -34,6 +34,13 @@ class CombinedSelectorNode implements NodeInterface protected $combinator; protected $subselector; + /** + * The constructor. + * + * @param NodeInterface $selector The XPath selector + * @param string $combinator The combinator + * @param NodeInterface $subselector The sub XPath selector + */ public function __construct($selector, $combinator, $subselector) { $this->selector = $selector; @@ -41,6 +48,9 @@ public function __construct($selector, $combinator, $subselector) $this->subselector = $subselector; } + /** + * {@inheritDoc} + */ public function __toString() { $comb = $this->combinator == ' ' ? '' : $this->combinator; @@ -49,6 +59,7 @@ public function __toString() } /** + * {@inheritDoc} * @throws SyntaxError When unknown combinator is found */ public function toXpath() @@ -63,6 +74,12 @@ public function toXpath() return $this->$method($path, $this->subselector); } + /** + * Joins a NodeInterface into the XPath of this object. + * + * @param XPathExpr $xpath The XPath expression for this object + * @param NodeInterface $sub The NodeInterface object to add + */ protected function _xpath_descendant($xpath, $sub) { // when sub is a descendant in any way of xpath @@ -71,6 +88,12 @@ protected function _xpath_descendant($xpath, $sub) return $xpath; } + /** + * Joins a NodeInterface as a child of this object. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The NodeInterface object to add + */ protected function _xpath_child($xpath, $sub) { // when sub is an immediate child of xpath @@ -79,6 +102,12 @@ protected function _xpath_child($xpath, $sub) return $xpath; } + /** + * Joins an XPath expression as an adjacent of another. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The adjacent XPath expression + */ protected function _xpath_direct_adjacent($xpath, $sub) { // when sub immediately follows xpath @@ -89,6 +118,12 @@ protected function _xpath_direct_adjacent($xpath, $sub) return $xpath; } + /** + * Joins an XPath expression as an indirect adjacent of another. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The indirect adjacent NodeInterface object + */ protected function _xpath_indirect_adjacent($xpath, $sub) { // when sub comes somewhere after xpath as a sibling diff --git a/src/Symfony/Component/CssSelector/Node/ElementNode.php b/src/Symfony/Component/CssSelector/Node/ElementNode.php index 86ffd57721091..683d36562496b 100644 --- a/src/Symfony/Component/CssSelector/Node/ElementNode.php +++ b/src/Symfony/Component/CssSelector/Node/ElementNode.php @@ -26,17 +26,31 @@ class ElementNode implements NodeInterface protected $namespace; protected $element; + /** + * Constructor. + * + * @param string $namespace Namespace + * @param string $element Element + */ public function __construct($namespace, $element) { $this->namespace = $namespace; $this->element = $element; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s]', __CLASS__, $this->formatElement()); } + /** + * Formats the element into a string. + * + * @return string Element as an XPath string + */ public function formatElement() { if ($this->namespace == '*') { @@ -46,6 +60,9 @@ public function formatElement() return sprintf('%s|%s', $this->namespace, $this->element); } + /** + * {@inheritDoc} + */ public function toXpath() { if ($this->namespace == '*') { diff --git a/src/Symfony/Component/CssSelector/Node/FunctionNode.php b/src/Symfony/Component/CssSelector/Node/FunctionNode.php index 9f7467b37d23c..8de049672d80e 100644 --- a/src/Symfony/Component/CssSelector/Node/FunctionNode.php +++ b/src/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -31,6 +31,14 @@ class FunctionNode implements NodeInterface protected $name; protected $expr; + /** + * Constructor. + * + * @param NodeInterface $selector The XPath expression + * @param string $type + * @param string $name + * @param XPathExpr $expr + */ public function __construct($selector, $type, $name, $expr) { $this->selector = $selector; @@ -39,12 +47,16 @@ public function __construct($selector, $type, $name, $expr) $this->expr = $expr; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s%s%s(%s)]', __CLASS__, $this->selector, $this->type, $this->name, $this->expr); } /** + * {@inheritDoc} * @throws SyntaxError When unsupported or unknown pseudo-class is found */ public function toXpath() @@ -61,6 +73,15 @@ public function toXpath() return $this->$method($sel_path, $this->expr); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param mixed $expr + * @param string $last + * @param string $addNameTest + * @return XPathExpr + */ protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = true) { list($a, $b) = $this->parseSeries($expr); @@ -124,11 +145,25 @@ protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = -1n+6 means elements 6 and previous */ } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_last_child($xpath, $expr) { return $this->_xpath_nth_child($xpath, $expr, true); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_of_type($xpath, $expr) { if ($xpath->getElement() == '*') { @@ -138,11 +173,25 @@ protected function _xpath_nth_of_type($xpath, $expr) return $this->_xpath_nth_child($xpath, $expr, false, false); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_last_of_type($xpath, $expr) { return $this->_xpath_nth_child($xpath, $expr, true, false); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_contains($xpath, $expr) { // text content, minus tags, must contain expr @@ -159,6 +208,13 @@ protected function _xpath_contains($xpath, $expr) return $xpath; } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_not($xpath, $expr) { // everything for which not expr applies @@ -170,7 +226,12 @@ protected function _xpath_not($xpath, $expr) return $xpath; } - // Parses things like '1n+2', or 'an+b' generally, returning (a, b) + /** + * Parses things like '1n+2', or 'an+b' generally, returning (a, b) + * + * @param mixed $s + * @return array + */ protected function parseSeries($s) { if ($s instanceof ElementNode) { diff --git a/src/Symfony/Component/CssSelector/Node/HashNode.php b/src/Symfony/Component/CssSelector/Node/HashNode.php index 0e72ce0d6ccf6..8c557b547cc58 100644 --- a/src/Symfony/Component/CssSelector/Node/HashNode.php +++ b/src/Symfony/Component/CssSelector/Node/HashNode.php @@ -26,17 +26,29 @@ class HashNode implements NodeInterface protected $selector; protected $id; + /** + * Constructor. + * + * @param NodeInterface $selector The NodeInterface object + * @param string $id The ID + */ public function __construct($selector, $id) { $this->selector = $selector; $this->id = $id; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s#%s]', __CLASS__, $this->selector, $this->id); } + /** + * {@inheritDoc} + */ public function toXpath() { $path = $this->selector->toXpath(); diff --git a/src/Symfony/Component/CssSelector/Node/NodeInterface.php b/src/Symfony/Component/CssSelector/Node/NodeInterface.php index f1e8392d045f5..2249617688b12 100644 --- a/src/Symfony/Component/CssSelector/Node/NodeInterface.php +++ b/src/Symfony/Component/CssSelector/Node/NodeInterface.php @@ -21,7 +21,17 @@ */ interface NodeInterface { + /** + * Returns a string representation of the object. + * + * @return string The string representation + */ function __toString(); + /** + * @return XPathExpr The XPath expression + * + * @throws SyntaxError When unknown operator is found + */ function toXpath(); } diff --git a/src/Symfony/Component/CssSelector/Node/OrNode.php b/src/Symfony/Component/CssSelector/Node/OrNode.php index c0ea77569502a..af13a6caff50c 100644 --- a/src/Symfony/Component/CssSelector/Node/OrNode.php +++ b/src/Symfony/Component/CssSelector/Node/OrNode.php @@ -25,16 +25,27 @@ class OrNode implements NodeInterface { protected $items; + /** + * Constructor. + * + * @param array $items An array of NodeInterface objects + */ public function __construct($items) { $this->items = $items; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s(%s)', __CLASS__, $this->items); } + /** + * {@inheritDoc} + */ public function toXpath() { $paths = array(); diff --git a/src/Symfony/Component/CssSelector/Node/PseudoNode.php b/src/Symfony/Component/CssSelector/Node/PseudoNode.php index d55009d68d34c..d0d9166ca4283 100644 --- a/src/Symfony/Component/CssSelector/Node/PseudoNode.php +++ b/src/Symfony/Component/CssSelector/Node/PseudoNode.php @@ -34,6 +34,11 @@ class PseudoNode implements NodeInterface protected $ident; /** + * Constructor. + * + * @param NodeInterface $element The NodeInterface element + * @param string $type Node type + * @param string $ident The ident * @throws SyntaxError When incorrect PseudoNode type is given */ public function __construct($element, $type, $ident) @@ -48,12 +53,16 @@ public function __construct($element, $type, $ident) $this->ident = $ident; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s%s%s]', __CLASS__, $this->element, $this->type, $this->ident); } /** + * {@inheritDoc} * @throws SyntaxError When unsupported or unknown pseudo-class is found */ public function toXpath() @@ -71,6 +80,11 @@ public function toXpath() return $this->$method($el_xpath); } + /** + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified XPath expression + */ protected function xpath_checked($xpath) { // FIXME: is this really all the elements? @@ -80,6 +94,8 @@ protected function xpath_checked($xpath) } /** + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified XPath expression * @throws SyntaxError If this element is the root element */ protected function xpath_root($xpath) @@ -88,6 +104,12 @@ protected function xpath_root($xpath) throw new SyntaxError(); } + /** + * Marks this XPath expression as the first child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_first_child($xpath) { $xpath->addStarPrefix(); @@ -97,6 +119,12 @@ protected function xpath_first_child($xpath) return $xpath; } + /** + * Sets the XPath to be the last child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_last_child($xpath) { $xpath->addStarPrefix(); @@ -106,6 +134,12 @@ protected function xpath_last_child($xpath) return $xpath; } + /** + * Sets the XPath expression to be the first of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_first_of_type($xpath) { if ($xpath->getElement() == '*') { @@ -118,6 +152,10 @@ protected function xpath_first_of_type($xpath) } /** + * Sets the XPath expression to be the last of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression * @throws SyntaxError Because *:last-of-type is not implemented */ protected function xpath_last_of_type($xpath) @@ -131,6 +169,12 @@ protected function xpath_last_of_type($xpath) return $xpath; } + /** + * Sets the XPath expression to be the only child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_only_child($xpath) { $xpath->addNameTest(); @@ -141,6 +185,10 @@ protected function xpath_only_child($xpath) } /** + * Sets the XPath expression to be only of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression * @throws SyntaxError Because *:only-of-type is not implemented */ protected function xpath_only_of_type($xpath) @@ -153,6 +201,12 @@ protected function xpath_only_of_type($xpath) return $xpath; } + /** + * undocumented function + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_empty($xpath) { $xpath->addCondition('not(*) and not(normalize-space())'); diff --git a/src/Symfony/Component/CssSelector/Parser.php b/src/Symfony/Component/CssSelector/Parser.php index 6861f80899b6a..612e556d2607b 100644 --- a/src/Symfony/Component/CssSelector/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser.php @@ -25,7 +25,16 @@ class Parser { /** + * Translates 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::') } /** + * Parses an expression and returns 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) } } + /** + * Parses a selector group contained in $stream and returns + * 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) } /** + * Parses a selector contained in $stream and returns 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) } /** + * Parses a simple selector (the current token) from $stream and returns + * 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) } /** + * Parses an attribute from a selector contained in $stream and returns + * 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..7f1fdfb4f291d 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; } + /** + * Answers 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..bbfbd88213a8f 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; } + /** + * Peeks 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..d6f934f5f5f96 100644 --- a/src/Symfony/Component/CssSelector/Tokenizer.php +++ b/src/Symfony/Component/CssSelector/Tokenizer.php @@ -21,6 +21,14 @@ */ class Tokenizer { + /** + * Takes a CSS selector and returns 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) } /** + * Tokenizes a quoted string (i.e. 'A string quoted with \' characters'), + * and returns 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) } /** + * Unescapes a string literal and returns 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) } /** + * Lexes selector $s and returns 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..d5a54caf06307 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; } + /** + * Answers 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; } + /** + * Adds 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) } } + /** + * Adds 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) } } + /** + * Adds 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 = '*'; } + /** + * Adds 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; } + /** + * Joins 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..e5b75a5a0a96a 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,56 @@ 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) { + // prototype, and key is irrelevant, so simply append the element + if (null !== $this->prototype && null === $this->keyAttribute) { + $leftSide[] = $v; + continue; + } + + // 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..3f270bf14cf24 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php @@ -2,10 +2,48 @@ 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. + * + * The following values are considered scalars: + * * booleans + * * strings + * * null + * * integers + * * floats + * + * @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; @@ -13,8 +51,8 @@ public function setName($name) protected function validateType($value) { - if (!is_scalar($value)) { - throw new \InvalidTypeException(sprintf( + if (!is_scalar($value) && null !== $value) { + throw new InvalidTypeException(sprintf( 'Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), json_encode($value) @@ -22,8 +60,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/Form/DateField.php b/src/Symfony/Component/Form/DateField.php index 3330bad81f765..f08c4733e976f 100644 --- a/src/Symfony/Component/Form/DateField.php +++ b/src/Symfony/Component/Form/DateField.php @@ -89,6 +89,19 @@ class DateField extends HybridField */ protected $formatter; + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + protected function configure() { $this->addOption('widget', self::CHOICE, self::$widgets); diff --git a/src/Symfony/Component/Form/DateTimeField.php b/src/Symfony/Component/Form/DateTimeField.php index 3002b7f2bfb7c..5f9d96ed8d13e 100644 --- a/src/Symfony/Component/Form/DateTimeField.php +++ b/src/Symfony/Component/Form/DateTimeField.php @@ -75,6 +75,19 @@ class DateTimeField extends Form TimeField::INPUT, ); + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + protected function configure() { $this->addOption('date_widget', DateField::CHOICE, self::$dateWidgets); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 820772bd1bbd3..1cd9f7ff262fb 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -58,6 +58,12 @@ class Form extends Field implements \IteratorAggregate, FormInterface */ protected $dataClass; + /** + * Stores the constructor closure for creating new domain object instances + * @var \Closure + */ + protected $dataConstructor; + /** * The context used when creating the form * @var FormContext @@ -86,6 +92,7 @@ public static function create(FormContextInterface $context, $name = null, array public function __construct($name = null, array $options = array()) { $this->addOption('data_class'); + $this->addOption('data_constructor'); $this->addOption('csrf_field_name', '_token'); $this->addOption('csrf_provider'); $this->addOption('field_factory'); @@ -93,6 +100,7 @@ public function __construct($name = null, array $options = array()) $this->addOption('virtual', false); $this->addOption('validator'); $this->addOption('context'); + $this->addOption('by_reference', true); if (isset($options['validation_groups'])) { $options['validation_groups'] = (array)$options['validation_groups']; @@ -102,6 +110,10 @@ public function __construct($name = null, array $options = array()) $this->dataClass = $options['data_class']; } + if (isset($options['data_constructor'])) { + $this->dataConstructor = $options['data_constructor']; + } + parent::__construct($name, $options); // Enable CSRF protection @@ -340,6 +352,16 @@ protected function getFieldsByVisibility($hidden, $recursive) */ public function setData($data) { + if (empty($data)) { + if ($this->dataConstructor) { + $constructor = $this->dataConstructor; + $data = $constructor(); + } else if ($this->dataClass) { + $class = $this->dataClass; + $data = new $class(); + } + } + parent::setData($data); // get transformed data and pass its values to child fields @@ -712,6 +734,10 @@ public function getCsrfProvider() */ public function bind(Request $request, $data = null) { + if (!$this->getName()) { + throw new FormException('You cannot bind anonymous forms. Please give this form a name'); + } + // Store object from which to read the default values and where to // write the submitted values if (null !== $data) { @@ -880,6 +906,21 @@ public function validateData(ExecutionContext $context) } } + /** + * {@inheritDoc} + */ + public function writeProperty(&$objectOrArray) + { + $data = $this->getData(); + + // Don't update parent if data is a composite type (object or array) + // and "by_reference" option is true, because then we expect that + // we are working with a reference to the parent's data + if (!is_object($data) || !is_object($objectOrArray) || !$this->getOption('by_reference')) { + parent::writeProperty($objectOrArray); + } + } + /** * Merges two arrays without reindexing numeric keys. * diff --git a/src/Symfony/Component/Form/TimeField.php b/src/Symfony/Component/Form/TimeField.php index 3f221db2b4bf3..a8961fe7aa830 100644 --- a/src/Symfony/Component/Form/TimeField.php +++ b/src/Symfony/Component/Form/TimeField.php @@ -59,6 +59,19 @@ class TimeField extends Form self::RAW, ); + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + /** * {@inheritDoc} */ diff --git a/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php index 1a793f5fce149..890b0bc326bfa 100644 --- a/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php @@ -47,7 +47,7 @@ public function transform($value) * @param string $value String value. * @return Boolean Boolean value. */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php deleted file mode 100644 index b939f69236dda..0000000000000 --- a/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php +++ /dev/null @@ -1,157 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\ValueTransformer; - -use Symfony\Component\Form\Configurable; -use Doctrine\Common\Collections\Collection; - -/** - * Transforms an instance of Doctrine\Common\Collections\Collection into a string of unique names. - * - * Use-Cases for this transformer include: List of Tag-Names, List Of Group/User-Names or the like. - * - * This transformer only makes sense if you know the list of related collections to be small and - * that they have a unique identifier field that is of meaning to the user (Tag Names) and is - * enforced to be unique in the storage. - * - * This transformer can cause the following SQL operations to happen in the case of an ORM collection: - * 1. Initialize the whole collection using one SELECT query - * 2. For each removed element issue an UPDATE or DELETE stmt (depending on one-to-many or many-to-many) - * 3. For each inserted element issue an INSERT or UPDATE stmt (depending on one-to-many or many-to-many) - * 4. Extra updates if necessary by the ORM. - * - * @todo Refactor to make 'fieldName' optional (identifier). - * - * @author Benjamin Eberlei - * @author Bernhard Schussek - */ -class CollectionToStringTransformer extends Configurable implements ValueTransformerInterface -{ - protected function configure() - { - $this->addOption('trim', true); - $this->addOption('separator', ','); - $this->addOption('explode_callback', 'explode'); - $this->addOption('implode_callback', 'implode'); - $this->addOption('create_instance_callback', null); - $this->addRequiredOption('em'); - $this->addRequiredOption('class_name'); - $this->addRequiredOption('field_name'); - - parent::configure(); - } - - /** - * @param string $value - * @param Collection $collection - */ - public function reverseTransform($value, $collection) - { - if (strlen(trim($value)) == 0) { - // don't check for collection count, a straight clear doesnt initialize the collection - $collection->clear(); - return $collection; - } - - $callback = $this->getOption('explode_callback'); - $values = call_user_func($callback, $this->getOption('separator'), $value); - - if ($this->getOption('trim') === true) { - $values = array_map('trim', $values); - } - - /* @var $em Doctrine\ORM\EntityManager */ - $em = $this->getOption('em'); - $className = $this->getOption('class_name'); - $reflField = $em->getClassMetadata($className) - ->getReflectionProperty($this->getOption('field_name')); - - // 1. removing elements that are not yet present anymore - foreach ($collection as $object) { - $uniqueIdent = $reflField->getValue($object); - $key = array_search($uniqueIdent, $values); - if (false === $key) { - $collection->removeElement($object); - } else { - // found in the collection, no need to do anything with it so remove it - unset($values[$key]); - } - } - - // 2. add elements that are known to the EntityManager but newly connected, query them from the repository - if (count($values)) { - $dql = sprintf('SELECT o FROM %s o WHERE o.%s IN (', $className, $this->getOption('field_name')); - $query = $em->createQuery(); - $needles = array(); - $i = 0; - foreach ($values as $val) { - $query->setParameter(++$i, $val); - $needles[] = '?'.$i; - } - $dql .= implode(',', $needles).')'; - $query->setDql($dql); - $newElements = $query->getResult(); - - foreach ($newElements as $object) { - $collection->add($object); - - $uniqueIdent = $reflField->getValue($object); - $key = array_search($uniqueIdent, $values); - unset($values[$key]); - } - } - - // 3. new elements that are not in the repository have to be created and persisted then attached: - if (count($values)) { - $callback = $this->getOption('create_instance_callback'); - if (!$callback || !is_callable($callback)) { - throw new TransformationFailedException('Cannot transform list of identifiers, because a new element was detected and it is unknown how to create an instance of this element.'); - } - - foreach ($values as $newValue) { - $newInstance = call_user_func($callback, $newValue); - if (!($newInstance instanceof $className)) { - throw new TransformationFailedException(sprintf('Error while trying to create a new instance for the identifier "%s". No new instance was created.', $newValue)); - } - $collection->add($newInstance); - $em->persist($newInstance); - } - } - - return $collection; - } - - /** - * Transform a Doctrine Collection into a string of identifies with a separator. - * - * @param Collection $value - * @return string - */ - public function transform($value) - { - if (null === $value) { - return ''; - } - - $values = array(); - $em = $this->getOption('em'); - $reflField = $em->getClassMetadata($this->getOption('class_name')) - ->getReflectionProperty($this->getOption('field_name')); - - foreach ($value as $object) { - $values[] = $reflField->getValue($object); - } - $callback = $this->getOption('implode_callback'); - - return call_user_func($callback, $this->getOption('separator'), $values); - } -} diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php index 1684a10b22a69..e66085c8ee6de 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php @@ -95,7 +95,7 @@ public function transform($dateTime) * @param array $value Localized date string/array * @return DateTime Normalized date */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (null === $value) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php index fd2bb2756e69d..e5cc1ec2268e1 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php @@ -86,7 +86,7 @@ public function transform($dateTime) * @param string|array $value Localized date string/array * @return DateTime Normalized date */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { $inputTimezone = $this->getOption('input_timezone'); diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php index 014b39c4b31f5..17b86d7be468a 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php @@ -62,7 +62,7 @@ public function transform($value) * @param string $value A value as produced by PHP's date() function * @return DateTime A DateTime object */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (empty($value)) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php index 3be5fc6c115e3..c05575f0da0b7 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php @@ -60,7 +60,7 @@ public function transform($value) * @param string $value A value as produced by PHP's date() function * @return DateTime A DateTime object */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (null === $value) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php index 15acd97207cb3..8db5b4cad9b31 100644 --- a/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php @@ -58,9 +58,9 @@ public function transform($value) * @param string $value Localized money string * @return number Normalized number */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { - $value = parent::reverseTransform($value, $originalValue); + $value = parent::reverseTransform($value); if (null !== $value) { $value *= $this->getOption('divisor'); diff --git a/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php index a960dbdf62c8f..19d9e3012d5f6 100644 --- a/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php @@ -74,7 +74,7 @@ public function transform($value) * * @param string $value */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php index a3762ee788ffb..ece2e4da90476 100644 --- a/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php @@ -82,7 +82,7 @@ public function transform($value) * @param number $value Percentage value. * @return number Normalized value. */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php b/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php index 93c068b81839f..99af8c16da5b6 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php @@ -48,7 +48,7 @@ public function transform($value) /** * {@inheritDoc} */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { return $this->reversedTransformer->transform($value); } diff --git a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php index a8bbda4dbfba3..129df2dc34c7d 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php +++ b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php @@ -66,10 +66,10 @@ public function transform($value) * @param mixed $value The transformed value * @return mixed The reverse-transformed value */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { for ($i = count($this->transformers) - 1; $i >= 0; --$i) { - $value = $this->transformers[$i]->reverseTransform($value, $originalValue); + $value = $this->transformers[$i]->reverseTransform($value); } return $value; diff --git a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php index 663011d5b8ecd..b170b7412a2ea 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php +++ b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php @@ -65,11 +65,9 @@ function transform($value); * is passed. * * @param mixed $value The value in the transformed representation - * @param mixed $originalValue The original value from the datasource that is about to be overwritten by the new value. - * @return mixed The value in the original representation * @throws UnexpectedTypeException when the argument is not of the * expected type * @throws ValueTransformerException when the transformation fails */ - function reverseTransform($value, $originalValue); + function reverseTransform($value); } \ No newline at end of file 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/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php index 7ea2d59cdfa7d..0aee4cd978cee 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -50,17 +50,10 @@ public function guess($path) return null; } - if (!$finfo = new \finfo(FILEINFO_MIME)) { + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE)) { return null; } - $type = $finfo->file($path); - - // remove charset (added as of PHP 5.3) - if (false !== $pos = strpos($type, ';')) { - $type = substr($type, 0, $pos); - } - - return $type; + return $finfo->file($path); } } \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 18a3a1c11583f..57883272c457b 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -223,14 +223,34 @@ static public function create($uri, $method = 'GET', $parameters = array(), $coo public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; - $dup->initialize( - null !== $query ? $query : $this->query->all(), - null !== $request ? $request : $this->request->all(), - null !== $attributes ? $attributes : $this->attributes->all(), - null !== $cookies ? $cookies : $this->cookies->all(), - null !== $files ? $files : $this->files->all(), - null !== $server ? $server : $this->server->all() - ); + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $this->languages = null; + $this->charsets = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; return $dup; } 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/Controller/ControllerResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php index fc7b1de3238e0..0f44beb246abf 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php @@ -49,6 +49,8 @@ function getController(Request $request); * @param Request $request A Request instance * @param mixed $controller A PHP callable * + * @return array An array of arguments to pass to the controller + * * @throws \RuntimeException When value for argument given is not provided */ function getArguments(Request $request, $controller); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 0af971fbe8b0a..dfe47a59cbd89 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -39,6 +39,11 @@ public function collect(Request $request, Response $response, \Exception $except $responseHeaders['Set-Cookie'] = $cookies; } + $attributes = array(); + foreach ($request->attributes->all() as $key => $value) { + $attributes[$key] = is_object($value) ? sprintf('Object(%s)', get_class($value)) : $value; + } + $this->data = array( 'format' => $request->getRequestFormat(), 'content_type' => $response->headers->get('Content-Type') ? $response->headers->get('Content-Type') : 'text/html', @@ -48,7 +53,7 @@ public function collect(Request $request, Response $response, \Exception $except 'request_headers' => $request->headers->all(), 'request_server' => $request->server->all(), 'request_cookies' => $request->cookies->all(), - 'request_attributes' => $request->attributes->all(), + 'request_attributes' => $attributes, 'response_headers' => $responseHeaders, 'session_attributes' => $request->hasSession() ? $request->getSession()->getAttributes() : array(), ); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index f4359f0285ea9..cadbfa9f83263 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -31,6 +31,7 @@ class HttpCache implements HttpKernelInterface protected $store; protected $request; protected $esi; + protected $esiTtls; /** * Constructor. @@ -136,6 +137,7 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ if (HttpKernelInterface::MASTER_REQUEST === $type) { $this->traces = array(); $this->request = $request; + $this->esiTtls = array(); } $path = $request->getPathInfo(); @@ -160,9 +162,44 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $response->headers->set('X-Symfony-Cache', $this->getLog()); } + if (null !== $this->esi) { + $this->addEsiTtl($response); + + if ($request === $this->request) { + $this->updateResponseCacheControl($response); + } + } + return $response; } + /** + * Stores the response's TTL locally. + * + * @param Response $response + */ + protected function addEsiTtl(Response $response) + { + $this->esiTtls[] = $response->isValidateable() ? -1 : $response->getTtl(); + } + + /** + * Changes the master response TTL to the smallest TTL received or force validation if + * one of the ESI has validation cache strategy. + * + * @param Response $response + */ + protected function updateResponseCacheControl(Response $response) + { + $ttl = min($this->esiTtls); + if (-1 === $ttl) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + } else { + $response->setSharedMaxAge($ttl); + $response->setMaxAge(0); + } + } + /** * Forwards the Request to the backend without storing the Response in the cache. * diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c46d9bf1a150c..86b7ebbe9dc24 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -97,7 +97,7 @@ public function boot() // init container $this->initializeContainer(); - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundle->setContainer($this->container); $bundle->boot(); } @@ -114,7 +114,7 @@ public function shutdown() { $this->booted = false; - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundle->shutdown(); $bundle->setContainer(null); } @@ -131,7 +131,17 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $this->boot(); } - return $this->container->get('http_kernel')->handle($request, $type, $catch); + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a http kernel from the container + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); } /** @@ -153,7 +163,7 @@ public function getBundles() */ public function isClassInActiveBundle($class) { - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundleClass = get_class($bundle); if (0 === strpos($class, substr($bundleClass, 0, strrpos($bundleClass, '\\')))) { return true; @@ -237,7 +247,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; } @@ -343,7 +353,7 @@ protected function initializeBundles() $this->bundles = array(); $topMostBundles = array(); $directChildren = array(); - + foreach ($this->registerBundles() as $bundle) { $name = $bundle->getName(); if (isset($this->bundles[$name])) { @@ -358,11 +368,11 @@ protected function initializeBundles() $directChildren[$parentName] = $name; } else { $topMostBundles[$name] = $bundle; - } + } } // look for orphans - if (count($diff = array_diff(array_keys($directChildren), array_keys($this->bundles)))) { + if (count($diff = array_values(array_diff(array_keys($directChildren), array_keys($this->bundles))))) { throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); } @@ -377,7 +387,7 @@ protected function initializeBundles() array_unshift($bundleMap, $this->bundles[$name]); $hierarchy[] = $name; } - + foreach ($hierarchy as $bundle) { $this->bundleMap[$bundle] = $bundleMap; array_pop($bundleMap); diff --git a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php index 25d4cc1f5f77c..0840f8649d22d 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php @@ -94,11 +94,11 @@ public function write($token, $data, $ip, $url, $time) ); try { $this->exec($db, 'INSERT INTO data (token, data, ip, url, time, created_at) VALUES (:token, :data, :ip, :url, :time, :created_at)', $args); + $this->cleanup(); $status = true; } catch (\Exception $e) { $status = false; } - $this->cleanup(); $this->close($db); return $status; @@ -146,6 +146,11 @@ protected function initDb() protected function exec($db, $query, array $args = array()) { $stmt = $db->prepare($query); + + if (false === $stmt) { + throw new \RuntimeException('The database cannot successfully prepare the statement'); + } + if ($db instanceof \SQLite3) { foreach ($args as $arg => $val) { $stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); @@ -157,7 +162,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..75e70a9a93926 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; } @@ -1233,14 +1243,34 @@ static public function create($uri, $method = 'GET', $parameters = array(), $coo public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; - $dup->initialize( - null !== $query ? $query : $this->query->all(), - null !== $request ? $request : $this->request->all(), - null !== $attributes ? $attributes : $this->attributes->all(), - null !== $cookies ? $cookies : $this->cookies->all(), - null !== $files ? $files : $this->files->all(), - null !== $server ? $server : $this->server->all() - ); + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $this->languages = null; + $this->charsets = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; return $dup; } public function __clone() 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/src/Symfony/Component/Routing/Router.php b/src/Symfony/Component/Routing/Router.php index cc90ee8dc1ffc..1a39019abd799 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php @@ -35,8 +35,9 @@ class Router implements RouterInterface * * Available options: * - * * cache_dir: The cache directory (or null to disable caching) - * * debug: Whether to enable debugging or not (false by default) + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * resource_type: Type hint for the main resource (optional) * * @param LoaderInterface $loader A LoaderInterface instance * @param mixed $resource The main resource to load @@ -63,6 +64,7 @@ public function __construct(LoaderInterface $loader, $resource, array $options = 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, ); // check option names @@ -81,7 +83,7 @@ public function __construct(LoaderInterface $loader, $resource, array $options = public function getRouteCollection() { if (null === $this->collection) { - $this->collection = $this->loader->load($this->resource); + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); } return $this->collection; diff --git a/src/Symfony/Component/Templating/DelegatingEngine.php b/src/Symfony/Component/Templating/DelegatingEngine.php index 9c558961dea7f..8717ef0f3d620 100644 --- a/src/Symfony/Component/Templating/DelegatingEngine.php +++ b/src/Symfony/Component/Templating/DelegatingEngine.php @@ -103,6 +103,15 @@ public function supports($name) return false; } + /** + * Get an engine able to render the given template. + * + * @param string $name A template name + * + * @return EngineInterface The engine + * + * @throws \RuntimeException if no engine able to work with the template is found + */ protected function getEngine($name) { foreach ($this->engines as $engine) { diff --git a/src/Symfony/Component/Templating/PhpEngine.php b/src/Symfony/Component/Templating/PhpEngine.php index c38898383d671..4fbaf13bb06c4 100644 --- a/src/Symfony/Component/Templating/PhpEngine.php +++ b/src/Symfony/Component/Templating/PhpEngine.php @@ -221,6 +221,11 @@ public function addHelpers(array $helpers = array()) } } + /** + * Sets the helpers. + * + * @params Helper[] $helpers An array of helper + */ public function setHelpers(array $helpers) { $this->helpers = array(); @@ -286,7 +291,8 @@ public function extend($template) /** * Escapes a string by using the current charset. * - * @param mixed $value A variable to escape + * @param mixed $value A variable to escape + * @param string $context The context name * * @return string The escaped value */ @@ -440,6 +446,17 @@ function ($value) use ($that) ); } + /** + * Convert a string from one encoding to another. + * + * @param string $string The string to convert + * @param string $to The input encoding + * @param string $from The output encoding + * + * @return string The string with the new encoding + * + * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring) + */ public function convertEncoding($string, $to, $from) { if (function_exists('iconv')) { diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index bb7f3057fc7e5..9cc7b5932b21a 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -379,6 +379,11 @@ static protected function evaluateScalar($scalar) } } + /** + * Get a regex that match a unix timestamp + * + * @return string The regular expression + */ static protected function getTimestampRegex() { return <<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..64fc6da4eda72 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php @@ -0,0 +1,170 @@ +root('root', 'array') + ->node('foo', 'scalar') + ->cannotBeOverwritten() + ->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') + ->canBeUnset() + ->node('foo', 'scalar')->end() + ->node('bar', 'scalar')->end() + ->end() + ->node('unsetted', 'array') + ->canBeUnset() + ->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)); + } + + public function testPrototypeWithoutAKeyAttribute() + { + $tb = new TreeBuilder(); + + $tree = $tb + ->root('config', 'array') + ->node('append_elements', 'array') + ->prototype('scalar')->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'append_elements' => array('a', 'b'), + ); + + $b = array( + 'append_elements' => array('c', 'd'), + ); + + $this->assertEquals(array('append_elements' => array('a', 'b', '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/Configuration/ScalarNodeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php new file mode 100644 index 0000000000000..8ff43ca073b46 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php @@ -0,0 +1,49 @@ +assertSame($value, $node->normalize($value)); + } + + public function getValidValues() + { + return array( + array(false), + array(true), + array(null), + array(''), + array('foo'), + array(0), + array(1), + array(0.0), + array(0.1), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new ScalarNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } +} \ No newline at end of file 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/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index 128fd50f6437d..da609f48b27ba 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -41,6 +41,59 @@ protected function configure() } } +// behaves like a form with a value transformer that transforms into +// a specific format +class FormTest_FormThatReturns extends Form +{ + protected $returnValue; + + public function setReturnValue($returnValue) + { + $this->returnValue = $returnValue; + } + + public function setData($data) + { + } + + public function getData() + { + return $this->returnValue; + } +} + +class FormTest_AuthorWithoutRefSetter +{ + protected $reference; + + protected $referenceCopy; + + public function __construct($reference) + { + $this->reference = $reference; + $this->referenceCopy = $reference; + } + + // The returned object should be modified by reference without having + // to provide a setReference() method + public function getReference() + { + return $this->reference; + } + + // The returned object is a copy, so setReferenceCopy() must be used + // to update it + public function getReferenceCopy() + { + return is_object($this->referenceCopy) ? clone $this->referenceCopy : $this->referenceCopy; + } + + public function setReferenceCopy($reference) + { + $this->referenceCopy = $reference; + } +} + class TestSetDataBeforeConfigureForm extends Form { protected $testCase; @@ -223,6 +276,16 @@ public function testValidationGroupsAreNotInheritedFromParentIfSet() $this->assertEquals(array('group2'), $childForm->getValidationGroups()); } + /** + * @expectedException Symfony\Component\Form\Exception\FormException + */ + public function testBindThrowsExceptionIfAnonymous() + { + $form = new Form(null, array('validator' => $this->createMockValidator())); + + $form->bind($this->createPostRequest()); + } + public function testBindValidatesData() { $form = new Form('author', array( @@ -928,6 +991,37 @@ public function testSetDataMatchesAgainstDataClass_succeeds() $form->setData(new Author()); } + public function testSetDataToNull() + { + $form = new Form('author'); + $form->setData(null); + + $this->assertNull($form->getData()); + } + + public function testSetDataToNullCreatesObjectIfClassAvailable() + { + $form = new Form('author', array( + 'data_class' => 'Symfony\Tests\Component\Form\Fixtures\Author', + )); + $form->setData(null); + + $this->assertEquals(new Author(), $form->getData()); + } + + public function testSetDataToNullUsesDataConstructorOption() + { + $author = new Author(); + $form = new Form('author', array( + 'data_constructor' => function () use ($author) { + return $author; + } + )); + $form->setData(null); + + $this->assertSame($author, $form->getData()); + } + public function testSubmitUpdatesTransformedDataFromAllFields() { $originalAuthor = new Author(); @@ -1130,6 +1224,94 @@ public function testValidateDataDoesNotWalkScalars() $form->validateData($context); } + public function testSubformDoesntCallSetters() + { + $author = new FormTest_AuthorWithoutRefSetter(new Author()); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new Form('reference'); + $refForm->add(new TestField('firstName')); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + // reference has a getter, but not setter + 'reference' => array( + 'firstName' => 'Foo', + ) + ) + ))); + + $this->assertEquals('Foo', $author->getReference()->firstName); + } + + public function testSubformCallsSettersIfByReferenceIsFalse() + { + $author = new FormTest_AuthorWithoutRefSetter(new Author()); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new Form('referenceCopy', array('by_reference' => false)); + $refForm->add(new TestField('firstName')); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + // referenceCopy has a getter that returns a copy + 'referenceCopy' => array( + 'firstName' => 'Foo', + ) + ) + ))); + + // firstName can only be updated if setReferenceCopy() was called + $this->assertEquals('Foo', $author->getReferenceCopy()->firstName); + } + + public function testSubformCallsSettersIfReferenceIsScalar() + { + $author = new FormTest_AuthorWithoutRefSetter('scalar'); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new FormTest_FormThatReturns('referenceCopy'); + $refForm->setReturnValue('foobar'); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + 'referenceCopy' => array(), // doesn't matter actually + ) + ))); + + // firstName can only be updated if setReferenceCopy() was called + $this->assertEquals('foobar', $author->getReferenceCopy()); + } + + public function testSubformAlwaysInsertsIntoArrays() + { + $ref1 = new Author(); + $ref2 = new Author(); + $author = array('referenceCopy' => $ref1); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new FormTest_FormThatReturns('referenceCopy'); + $refForm->setReturnValue($ref2); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + 'referenceCopy' => array(), // doesn't matter actually + ) + ))); + + // the new reference was inserted into the array + $author = $form->getData(); + $this->assertSame($ref2, $author['referenceCopy']); + } + /** * Create a group containing two fields, "visibleField" and "hiddenField" * diff --git a/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php b/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php deleted file mode 100644 index 97565c4070cec..0000000000000 --- a/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php +++ /dev/null @@ -1,223 +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\Form\ValueTransformer; - -require_once __DIR__.'/../DoctrineOrmTestCase.php'; - -use Symfony\Tests\Component\Form\DoctrineOrmTestCase; -use Symfony\Component\Form\ValueTransformer\CollectionToStringTransformer; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Tools\SchemaTool; - -class CollectionToStringTransformerTest extends DoctrineOrmTestCase -{ - /** - * @var EntityManager - */ - private $em; - - protected function setUp() - { - parent::setUp(); - $this->em = $this->createTestEntityManager(); - - $schemaTool = new SchemaTool($this->em); - $classes = array($this->em->getClassMetadata(__NAMESPACE__.'\Tag')); - try { - $schemaTool->dropSchema($classes); - } catch(\Exception $e) { - - } - try { - $schemaTool->createSchema($classes); - } catch(\Exception $e) { - echo $e->getMessage(); - } - } - - public function testNoEntityManagerThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'field_name' => 'name', - )); - } - - public function testNoClassNameThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'field_name' => 'name', - 'em' => $this->em, - )); - } - - public function testNoFieldNameThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'em' => $this->em, - )); - } - - public function createTransformer() - { - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'field_name' => 'name', - 'em' => $this->em, - 'create_instance_callback' => function($tagName) { - return new Tag($tagName); - } - )); - return $transformer; - } - - public function testTransformEmptyCollection() - { - $transformer = $this->createTransformer(); - $ret = $transformer->transform(new ArrayCollection()); - - $this->assertEquals("", $ret); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testTransformCollection() - { - $transformer = $this->createTransformer(); - - $tags = new ArrayCollection(); - $tags->add(new Tag("foo")); - $tags->add(new Tag("bar")); - - $this->assertEquals("foo,bar", $transformer->transform($tags)); - } - - public function createTagCollection() - { - $tags = new ArrayCollection(); - $tags->add(new Tag("foo")); - $tags->add(new Tag("bar")); - - return $tags; - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformEmptyString() - { - $transformer = $this->createTransformer(); - - $col = new ArrayCollection(); - - $newCol = $transformer->reverseTransform("", $col); - $this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!"); - $this->assertEquals(0, count($newCol)); - } - - /** - * @depends testReverseTransformEmptyString - */ - public function testReverseTransformEmptyStringEmptiesCollection() - { - $transformer = $this->createTransformer(); - - $col = $this->createTagCollection(); - - $newCol = $transformer->reverseTransform("", $col); - $this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!"); - $this->assertEquals(0, count($newCol)); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformUnchanged() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - - $tags = $transformer->reverseTransform("foo,bar", $tags); - - $this->assertEquals(2, count($tags)); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformNewKnownEntity() - { - $transformer = $this->createTransformer(); - - $newTag = new Tag("baz"); - $this->em->persist($newTag); - $this->em->flush(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo, bar, baz", $tags); - - $this->assertEquals(3, count($tags)); - $this->assertTrue($tags->contains($newTag)); - } - - /** - * @depends testReverseTransformNewKnownEntity - */ - public function testReverseTransformNewUnknownEntity() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo, bar, baz", $tags); - - $this->assertEquals(3, count($tags)); - $this->em->flush(); - - $this->assertSame($this->em, $transformer->getOption('em')); - - $this->assertEquals(1, count($this->em->getRepository(__NAMESPACE__.'\Tag')->findAll())); - } - - /** - * @depends testReverseTransformNewUnknownEntity - */ - public function testReverseTransformRemoveEntity() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo", $tags); - - $this->assertEquals(1, count($tags)); - } - -} - -/** @Entity */ -class Tag -{ - /** @Id @GeneratedValue @Column(type="integer") */ - public $id; - - /** @Column(type="string") */ - public $name; - - public function __construct($name) { - $this->name = $name; - } -} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php b/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php new file mode 100644 index 0000000000000..7ab76a4c1291c --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Cookie; + +/** + * CookieTest + * + * @author John Kary + */ +class CookieTest extends \PHPUnit_Framework_TestCase +{ + public function invalidNames() + { + return array( + array(''), + array(",MyName"), + array(";MyName"), + array(" MyName"), + array("\tMyName"), + array("\rMyName"), + array("\nMyName"), + array("\013MyName"), + array("\014MyName"), + ); + } + + public function invalidValues() + { + return array( + array(",MyValue"), + array(";MyValue"), + array(" MyValue"), + array("\tMyValue"), + array("\rMyValue"), + array("\nMyValue"), + array("\013MyValue"), + array("\014MyValue"), + ); + } + + /** + * @dataProvider invalidNames + * @expectedException InvalidArgumentException + * @covers Symfony\Component\HttpFoundation\Cookie::__construct + */ + public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + { + new Cookie($name); + } + + /** + * @dataProvider invalidValues + * @expectedException InvalidArgumentException + * @covers Symfony\Component\HttpFoundation\Cookie::__construct + */ + public function testInstantiationThrowsExceptionIfCookieValueContainsInvalidCharacters($value) + { + new Cookie('MyCookie', $value); + } + + /** + * @covers Symfony\Component\HttpFoundation\Cookie::getValue + */ + public function testGetValue() + { + $value = 'MyValue'; + $cookie = new Cookie('MyCookie', $value); + + $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value'); + } +} 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/HttpFoundation/File/Fixtures/.unknownextension b/tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension new file mode 100644 index 0000000000000..4d1ae35ba2c8e --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php new file mode 100644 index 0000000000000..957f2c0375e59 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\ContentTypeMimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +class MimeTypeTest extends \PHPUnit_Framework_TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithContentTypeMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new ContentTypeMimeTypeGuesser()); + $this->assertEquals('image/gif', $guesser->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithFileBinaryMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new FileBinaryMimeTypeGuesser()); + $this->assertEquals('image/gif', $guesser->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithKnownExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } + + public function testGuessFileWithUnknownExtension() + { + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } + + public function testGuessWithIncorrectPath() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here'); + } + + public function testGuessWithNonReadablePath() + { + $path = __DIR__.'/../Fixtures/to_delete'; + touch($path); + chmod($path, 0333); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); + MimeTypeGuesser::getInstance()->guess($path); + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + chmod($path, 0666); + @unlink($path); + } + } +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php index 5e5cb5d69677a..fe2fa432a4302 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php @@ -15,6 +15,7 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase { + public function testFileUploadsMustBeEnabled() { // we can't change this setting without modifying php.ini :( @@ -31,6 +32,42 @@ public function testFileUploadsMustBeEnabled() } } + public function testFileUploadsWithNoMimeType() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); + $this->assertEquals('image/gif', $file->getMimeType()); + } + } + + public function testFileUploadsWithUnknownMimeType() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + + $file = new UploadedFile( + __DIR__.'/Fixtures/.unknownextension', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/.unknownextension'), + UPLOAD_ERR_OK + ); + + $this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); + $this->assertEquals('application/octet-stream', $file->getMimeType()); + } + } + public function testErrorIsOkByDefault() { // we can't change this setting without modifying php.ini :( @@ -46,4 +83,19 @@ public function testErrorIsOkByDefault() $this->assertEquals(UPLOAD_ERR_OK, $file->getError()); } } + public function testGetOriginalName() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getOriginalName()); + } + } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php index 59be4968a83d1..467e7c8735ea7 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php @@ -54,28 +54,37 @@ public function testCreate() $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('bar=baz', $request->getQueryString()); $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); $request = Request::create('https://test.com/foo?bar=baz'); $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('bar=baz', $request->getQueryString()); $this->assertEquals(443, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertTrue($request->isSecure()); $request = Request::create('test.com:90/foo'); $this->assertEquals('http://test.com:90/foo', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); $this->assertEquals(90, $request->getPort()); + $this->assertFalse($request->isSecure()); $request = Request::create('https://test.com:90/foo'); $this->assertEquals('https://test.com:90/foo', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json); $this->assertEquals($json, $request->getContent()); + $this->assertFalse($request->isSecure()); } /** @@ -101,6 +110,7 @@ public function testDuplicate() /** * @covers Symfony\Component\HttpFoundation\Request::getFormat + * @covers Symfony\Component\HttpFoundation\Request::setFormat * @dataProvider getFormatToMimeTypeMapProvider */ public function testGetFormatFromMimeType($format, $mimeTypes) @@ -109,7 +119,10 @@ public function testGetFormatFromMimeType($format, $mimeTypes) foreach ($mimeTypes as $mime) { $this->assertEquals($format, $request->getFormat($mime)); } - + $request->setFormat($format, $mimeTypes); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } } /** @@ -322,6 +335,7 @@ public function testGetUriForPath() $request->initialize(array(), array(), array(), array(), array(), $server); $this->assertEquals('http://hostname/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER'); + $this->assertEquals('hostname', $request->getHttpHost()); } /** @@ -420,6 +434,23 @@ public function testGetSetMethod() $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); } + public function testGetClientIp() + { + $request = new Request; + $this->assertEquals('', $request->getClientIp()); + $this->assertEquals('', $request->getClientIp(true)); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_CLIENT_IP' => '88.88.88.88')); + $this->assertEquals('127.0.0.1', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_CLIENT_IP' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp(true)); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '88.88.88.88')); + $this->assertEquals('127.0.0.1', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp(true)); + } + public function testGetContentWorksTwiceInDefaultMode() { $req = new Request; @@ -473,4 +504,163 @@ public function testCreateFromGlobals() unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); } + + public function testOverrideGlobals() + { + $time = $_SERVER['REQUEST_TIME']; // fix for phpunit timer + + $request = new Request(); + $request->initialize(array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + + $request->initialize(array(), array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_POST); + + $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('X_FORWARDED_PROTO', 'https'); + + $this->assertTrue($request->isSecure()); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $_SERVER['REQUEST_TIME'] = $time; // fix for phpunit timer + } + + public function testGetScriptName() + { + $request = new Request(); + $this->assertEquals('', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + + $server = array(); + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/frontend.php', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + } + + public function testGetBasePath() + { + $request = new Request(); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['PHP_SELF'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + } + + public function testGetPreferredLanguage() + { + $request = new Request(); + $this->assertEquals('', $request->getPreferredLanguage()); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr'))); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en'))); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr'))); + $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + } + + public function testIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + + $request->headers->remove('X-Requested-With'); + $this->assertFalse($request->isXmlHttpRequest()); + } + + public function testGetCharsets() + { + $request = new Request(); + $this->assertEquals(array(), $request->getCharsets()); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array(), $request->getCharsets()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets()); + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + $this->assertEquals(array('ISO-8859-1', '*', 'utf-8'), $request->getCharsets()); + } + + public function testGetAcceptableContentTypes() + { + $request = new Request(); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching + + $request = new Request(); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array('multipart/mixed', '*/*', 'text/html', 'application/xhtml+xml', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/vnd.wap.wmlscriptc'), $request->getAcceptableContentTypes()); + } + + public function testGetLanguages() + { + $request = new Request(); + $this->assertNull($request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6'); + $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages()); + } } 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/HttpCache/HttpCacheTest.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php index 4c0d7cfde9e42..d6a5ba7f2afaf 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php @@ -893,4 +893,67 @@ public function testShouldNotCatchExceptions() $this->assertExceptionsAreNotCaught(); } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals("Hello World! My name is Bobby.", $this->response->getContent()); + $this->assertEquals(100, $this->response->getTtl()); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertEquals(null, $this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } } diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php index 1af5811cc1808..a7654a5e162f5 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php @@ -12,8 +12,10 @@ namespace Symfony\Tests\Component\HttpKernel\HttpCache; require_once __DIR__.'/TestHttpKernel.php'; +require_once __DIR__.'/TestMultipleHttpKernel.php'; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; use Symfony\Component\HttpKernel\HttpCache\HttpCache; use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -28,12 +30,14 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase protected $response; protected $responses; protected $catch; + protected $esi; protected function setUp() { $this->kernel = null; $this->cache = null; + $this->esi = null; $this->caches = array(); $this->cacheConfig = array(); @@ -41,6 +45,7 @@ protected function setUp() $this->response = null; $this->responses = array(); + $this->catch = false; $this->clearDirectory(sys_get_temp_dir().'/http_cache'); @@ -101,7 +106,7 @@ public function assertExceptionsAreNotCaught() $this->assertFalse($this->kernel->isCatchingExceptions()); } - public function request($method, $uri = '/', $server = array(), $cookies = array()) + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false) { if (null === $this->kernel) { throw new \LogicException('You must call setNextResponse() before calling request().'); @@ -112,7 +117,9 @@ public function request($method, $uri = '/', $server = array(), $cookies = array $this->store = new Store(sys_get_temp_dir().'/http_cache'); $this->cacheConfig['debug'] = true; - $this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig); + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); @@ -133,11 +140,14 @@ public function getMetaStorageValues() // A basic response with 200 status code and a tiny body. public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) { - $called = false; - $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); } + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + public function catchExceptions($catch = true) { $this->catch = $catch; diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 0000000000000..899e8c2b57dd7 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface +{ + protected $bodies; + protected $statuses; + protected $headers; + protected $catch; + protected $call; + + public function __construct($responses) + { + $this->bodies = array(); + $this->statuses = array(); + $this->headers = array(); + $this->call = false; + + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this); + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->call = false; + } +} diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index 5695f5a6fdbaf..18542f50f7a30 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -13,10 +13,296 @@ use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\Loader\LoaderInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; class KernelTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + $kernel->boot(); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer') + ->with(null); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel', 'boot')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + // required as this value is initialized + // in the kernel constructor, which we don't call + $kernel->setIsBooted(false); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + if (!function_exists('token_get_all')) { + $this->markTestSkipped(); + return; + } + $source = <<assertEquals($expected, Kernel::stripComments($source)); + } + + public function testIsClassInActiveBundleFalse() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('Not\In\Active\Bundle')); + } + + public function testIsClassInActiveBundleFalseNoNamespace() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('NotNamespacedClass')); + } + + public function testIsClassInActiveBundleTrue() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertTrue($kernel->isClassInActiveBundle(__NAMESPACE__.'\FooBarBundle\SomeClass')); + } + + protected function getKernelMockForIsClassInActiveBundleTest() + { + $bundle = new FooBarBundle(); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + return $kernel; + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__, $kernel->getRootDir()); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('HttpKernel', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + /** * @expectedException \InvalidArgumentException */ @@ -241,21 +527,21 @@ public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWith { $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); - + $kernel = $this->getKernel(); $kernel ->expects($this->once()) ->method('registerBundles') ->will($this->returnValue(array($fooBundle, $barBundle))) ; - $kernel->initializeBundles(); + $kernel->initializeBundles(); } protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) { $bundle = $this ->getMockBuilder('Symfony\Tests\Component\HttpKernel\BundleForTest') - ->setMethods(array('getNormalizedPath', 'getParent', 'getName')) + ->setMethods(array('getPath', 'getParent', 'getName')) ->disableOriginalConstructor() ; @@ -273,16 +559,16 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu $bundle ->expects($this->any()) - ->method('getNormalizedPath') + ->method('getPath') ->will($this->returnValue(strtr($dir, '\\', '/'))) ; - + $bundle ->expects($this->any()) ->method('getParent') ->will($this->returnValue($parent)) ; - + return $bundle; } @@ -315,6 +601,7 @@ public function getBundleMap() public function registerRootDir() { + return __DIR__; } public function registerBundles() @@ -333,9 +620,24 @@ public function initializeBundles() { parent::initializeBundles(); } + + public function isBooted() + { + return $this->booted; + } + + public function setIsBooted($value) + { + $this->booted = (bool) $value; + } } abstract class BundleForTest implements BundleInterface { // We can not extend Symfony\Component\HttpKernel\Bundle\Bundle as we want to mock getName() which is final -} \ No newline at end of file +} + +class FooBarBundle extends Bundle +{ + // We need a full namespaced bundle instance to test isClassInActiveBundle +}