From cabb1fa8bb60948fe38afd37ddf70d854aae9f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 6 Sep 2013 10:47:41 +0200 Subject: [PATCH] [FrameworkBundle] Adds the possibility to register Commands via the DIC --- .../FrameworkBundle/Console/Application.php | 8 ++ .../Compiler/AddConsoleCommandPass.php | 49 ++++++++++ .../FrameworkBundle/FrameworkBundle.php | 2 + .../Tests/Console/ApplicationTest.php | 12 +++ .../Compiler/AddConsoleCommandPassTest.php | 96 +++++++++++++++++++ .../Component/HttpKernel/Bundle/Bundle.php | 9 +- .../HttpKernel/Tests/Bundle/BundleTest.php | 25 ++++- 7 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 9d7e4ff1a9..224dbea2e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -98,6 +98,14 @@ class Application extends BaseApplication protected function registerCommands() { + $container = $this->kernel->getContainer(); + + if ($container->hasParameter('console.command.ids')) { + foreach ($container->getParameter('console.command.ids') as $id) { + $this->add($container->get($id)); + } + } + foreach ($this->kernel->getBundles() as $bundle) { if ($bundle instanceof Bundle) { $bundle->registerCommands($this); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php new file mode 100644 index 0000000000..3c96761ba3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * AddConsoleCommandPass. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds('console.command'); + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + + if (!$definition->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be public.', $id)); + } + + if ($definition->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must not be abstract.', $id)); + } + + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + $r = new \ReflectionClass($class); + if (!$r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command')) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "Symfony\\Component\\Console\\Command\\Command".', $id)); + } + $container->setAlias('console.command.'.strtolower(str_replace('\\', '_', $class)), $id); + } + + $container->setParameter('console.command.ids', array_keys($commandServices)); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 3aaf22a623..5418121dd6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; @@ -71,6 +72,7 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new TemplatingPass()); $container->addCompilerPass(new AddConstraintValidatorsPass()); $container->addCompilerPass(new AddValidatorInitializersPass()); + $container->addCompilerPass(new AddConsoleCommandPass()); $container->addCompilerPass(new FormPass()); $container->addCompilerPass(new TranslatorPass()); $container->addCompilerPass(new AddCacheWarmerPass()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index 42fcb6f597..612d6325dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -74,6 +74,18 @@ class ApplicationTest extends TestCase ->with($this->equalTo('event_dispatcher')) ->will($this->returnValue($dispatcher)) ; + $container + ->expects($this->once()) + ->method('hasParameter') + ->with($this->equalTo('console.command.ids')) + ->will($this->returnValue(true)) + ; + $container + ->expects($this->once()) + ->method('getParameter') + ->with($this->equalTo('console.command.ids')) + ->will($this->returnValue(array())) + ; $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); $kernel diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php new file mode 100644 index 0000000000..84b3dcb6d4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php @@ -0,0 +1,96 @@ + + * + * 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\Compiler; + +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +class AddConsoleCommandPassTest extends \PHPUnit_Framework_TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + $container->setParameter('my-command.class', 'Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + + $definition = new Definition('%my-command.class%'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + + $container->compile(); + + $alias = 'console.command.symfony_bundle_frameworkbundle_tests_dependencyinjection_compiler_mycommand'; + $this->assertTrue($container->hasAlias($alias)); + $this->assertSame('my-command', (string) $container->getAlias($alias)); + + $this->assertTrue($container->hasParameter('console.command.ids')); + $this->assertSame(array('my-command'), $container->getParameter('console.command.ids')); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must be public. + */ + public function testProcessThrowAnExceptionIfTheServiceIsNotPublic() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + $definition->addTag('console.command'); + $definition->setPublic(false); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must not be abstract. + */ + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + $definition->addTag('console.command'); + $definition->setAbstract(true); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must be a subclass of "Symfony\Component\Console\Command\Command". + */ + public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('SplObjectStorage'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } +} + +class MyCommand extends Command +{ + +} diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php index 0608f13bab..d8c649b10a 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -187,7 +187,14 @@ abstract class Bundle extends ContainerAware implements BundleInterface if ($relativePath = $file->getRelativePath()) { $ns .= '\\'.strtr($relativePath, '/', '\\'); } - $r = new \ReflectionClass($ns.'\\'.$file->getBasename('.php')); + $class = $ns.'\\'.$file->getBasename('.php'); + if ($this->container) { + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if ($this->container->has($alias)) { + continue; + } + } + $r = new \ReflectionClass($class); if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { $application->add($r->newInstance()); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php b/src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php index 829e7a7d4f..a451b7aba0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php @@ -11,11 +11,13 @@ namespace Symfony\Component\HttpKernel\Tests\Bundle; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\HttpKernel\Bundle\Bundle; - -use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle\ExtensionAbsentBundle; use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; class BundleTest extends \PHPUnit_Framework_TestCase { @@ -31,6 +33,25 @@ class BundleTest extends \PHPUnit_Framework_TestCase $bundle2 = new ExtensionAbsentBundle(); $this->assertNull($bundle2->registerCommands($app)); + } + public function testRegisterCommandsIngoreCommandAsAService() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + $definition = new Definition('Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + $container->compile(); + + $application = $this->getMock('Symfony\Component\Console\Application'); + // Never called, because it's the + // Symfony\Bundle\FrameworkBundle\Console\Application that register + // commands as a service + $application->expects($this->never())->method('add'); + + $bundle = new ExtensionPresentBundle(); + $bundle->setContainer($container); + $bundle->registerCommands($application); } }