Skip to content

Commit

Permalink
[FrameworkBundle] Adds the possibility to register Commands via the DIC
Browse files Browse the repository at this point in the history
  • Loading branch information
lyrixx authored and fabpot committed Oct 4, 2013
1 parent 678e1de commit cabb1fa
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 3 deletions.
8 changes: 8 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Console/Application.php
Expand Up @@ -98,6 +98,14 @@ public function doRun(InputInterface $input, OutputInterface $output)

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);
Expand Down
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <lyrixx@lyrixx.info>
*/
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));
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Expand Up @@ -13,6 +13,7 @@

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;
Expand Down Expand Up @@ -71,6 +72,7 @@ public function build(ContainerBuilder $container)
$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());
Expand Down
Expand Up @@ -74,6 +74,18 @@ private function getKernel(array $bundles)
->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
Expand Down
@@ -0,0 +1,96 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
{

}
9 changes: 8 additions & 1 deletion src/Symfony/Component/HttpKernel/Bundle/Bundle.php
Expand Up @@ -187,7 +187,14 @@ public function registerCommands(Application $application)
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());
}
Expand Down
25 changes: 23 additions & 2 deletions src/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php
Expand Up @@ -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
{
Expand All @@ -31,6 +33,25 @@ public function testRegisterCommands()
$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);
}
}

0 comments on commit cabb1fa

Please sign in to comment.