diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index bae6cb0a5a3fd..2067942665051 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -18,6 +18,8 @@ CHANGELOG
`Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass` instead
* Deprecated `TranslatorPass`, use
`Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead
+ * Added `command` attribute to the `console.command` tag which takes the command
+ name as value, using it makes the command lazy
3.3.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
index af5eb25c5a2d6..b1c893ad0503d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
@@ -68,15 +68,7 @@ public function doRun(InputInterface $input, OutputInterface $output)
{
$this->kernel->boot();
- $container = $this->kernel->getContainer();
-
- foreach ($this->all() as $command) {
- if ($command instanceof ContainerAwareInterface) {
- $command->setContainer($container);
- }
- }
-
- $this->setDispatcher($container->get('event_dispatcher'));
+ $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher'));
return parent::doRun($input, $output);
}
@@ -98,7 +90,13 @@ public function get($name)
{
$this->registerCommands();
- return parent::get($name);
+ $command = parent::get($name);
+
+ if ($command instanceof ContainerAwareInterface) {
+ $command->setContainer($this->kernel->getContainer());
+ }
+
+ return $command;
}
/**
@@ -144,9 +142,15 @@ protected function registerCommands()
}
}
+ if ($container->has('console.command_loader')) {
+ $this->setCommandLoader($container->get('console.command_loader'));
+ }
+
if ($container->hasParameter('console.command.ids')) {
foreach ($container->getParameter('console.command.ids') as $id) {
- $this->add($container->get($id));
+ if (false !== $id) {
+ $this->add($container->get($id));
+ }
}
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php
index 597082485a0b6..13901dd1734b5 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php
@@ -77,10 +77,15 @@ private function getKernel()
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
- ->expects($this->once())
+ ->expects($this->atLeastOnce())
->method('has')
- ->with('router')
- ->will($this->returnValue(true))
+ ->will($this->returnCallback(function ($id) {
+ if ('console.command_loader' === $id) {
+ return false;
+ }
+
+ return true;
+ }))
;
$container
->expects($this->any())
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php
index 384bd7ca53079..44749bbd71dbc 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php
@@ -78,8 +78,14 @@ private function getKernel()
$container
->expects($this->atLeastOnce())
->method('has')
- ->with('router')
- ->will($this->returnValue(true));
+ ->will($this->returnCallback(function ($id) {
+ if ('console.command_loader' === $id) {
+ return false;
+ }
+
+ return true;
+ }))
+ ;
$container
->expects($this->any())
->method('get')
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index e17636e156175..20686669ddaa4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -35,7 +35,7 @@
"fig/link-util": "^1.0",
"symfony/asset": "~3.3|~4.0",
"symfony/browser-kit": "~2.8|~3.0|~4.0",
- "symfony/console": "~3.3|~4.0",
+ "symfony/console": "~3.4|~4.0",
"symfony/css-selector": "~2.8|~3.0|~4.0",
"symfony/dom-crawler": "~2.8|~3.0|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
@@ -64,7 +64,7 @@
"phpdocumentor/type-resolver": "<0.2.0",
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
"symfony/asset": "<3.3",
- "symfony/console": "<3.3",
+ "symfony/console": "<3.4",
"symfony/form": "<3.3",
"symfony/property-info": "<3.3",
"symfony/serializer": "<3.3",
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml
index d8b818ff61051..c94c00f75488f 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Symfony/Bundle/WebServerBundle/Resources/config/webserver.xml b/src/Symfony/Bundle/WebServerBundle/Resources/config/webserver.xml
index 2815c6d2cfbf5..a3c8b50841532 100644
--- a/src/Symfony/Bundle/WebServerBundle/Resources/config/webserver.xml
+++ b/src/Symfony/Bundle/WebServerBundle/Resources/config/webserver.xml
@@ -10,21 +10,21 @@
%kernel.project_dir%/web
%kernel.environment%
-
+
%kernel.project_dir%/web
%kernel.environment%
-
+
-
+
-
+
diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php
index fd15fb4cb26aa..fff5d0a6e7b8c 100644
--- a/src/Symfony/Component/Console/Application.php
+++ b/src/Symfony/Component/Console/Application.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Console;
+use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
@@ -64,6 +65,7 @@ class Application
private $runningCommand;
private $name;
private $version;
+ private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
@@ -96,6 +98,11 @@ public function setDispatcher(EventDispatcherInterface $dispatcher)
$this->dispatcher = $dispatcher;
}
+ public function setCommandLoader(CommandLoaderInterface $commandLoader)
+ {
+ $this->commandLoader = $commandLoader;
+ }
+
/**
* Runs the current application.
*
@@ -431,6 +438,10 @@ public function add(Command $command)
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
}
+ if (!$command->getName()) {
+ throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($command)));
+ }
+
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
@@ -451,12 +462,16 @@ public function add(Command $command)
*/
public function get($name)
{
- if (!isset($this->commands[$name])) {
+ if (isset($this->commands[$name])) {
+ $command = $this->commands[$name];
+ } elseif ($this->commandLoader && $this->commandLoader->has($name)) {
+ $command = $this->commandLoader->get($name);
+ $command->setName($name);
+ $this->add($command);
+ } else {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
- $command = $this->commands[$name];
-
if ($this->wantHelps) {
$this->wantHelps = false;
@@ -478,7 +493,7 @@ public function get($name)
*/
public function has($name)
{
- return isset($this->commands[$name]);
+ return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name));
}
/**
@@ -555,7 +570,7 @@ public function findNamespace($namespace)
*/
public function find($name)
{
- $allCommands = array_keys($this->commands);
+ $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
@@ -581,12 +596,12 @@ public function find($name)
// filter out aliases for commands which are already on the list
if (count($commands) > 1) {
- $commandList = $this->commands;
- $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
- $commandName = $commandList[$nameOrAlias]->getName();
+ $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
+ $commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
+ $commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
- });
+ }));
}
$exact = in_array($name, $commands, true);
@@ -598,6 +613,9 @@ public function find($name)
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
+ if (!$commandList[$cmd] instanceof Command) {
+ return $cmd;
+ }
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
@@ -622,7 +640,18 @@ public function find($name)
public function all($namespace = null)
{
if (null === $namespace) {
- return $this->commands;
+ if (!$this->commandLoader) {
+ return $this->commands;
+ }
+
+ $commands = $this->commands;
+ foreach ($this->commandLoader->getNames() as $name) {
+ if (!isset($commands[$name])) {
+ $commands[$name] = $this->commandLoader->get($name);
+ }
+ }
+
+ return $commands;
}
$commands = array();
@@ -632,6 +661,14 @@ public function all($namespace = null)
}
}
+ if ($this->commandLoader) {
+ foreach ($this->commandLoader->getNames() as $name) {
+ if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
+ $commands[$name] = $this->commandLoader->get($name);
+ }
+ }
+ }
+
return $commands;
}
diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md
index f8539491d264b..542d00623bebb 100644
--- a/src/Symfony/Component/Console/CHANGELOG.md
+++ b/src/Symfony/Component/Console/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+3.4.0
+-----
+
+ * added `CommandLoaderInterface` and PSR-11 `ContainerCommandLoader`
+
3.3.0
-----
diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php
index 08a74c3b69a9a..6620ff5718c53 100644
--- a/src/Symfony/Component/Console/Command/Command.php
+++ b/src/Symfony/Component/Console/Command/Command.php
@@ -61,10 +61,6 @@ public function __construct($name = null)
}
$this->configure();
-
- if (!$this->name) {
- throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
- }
}
/**
diff --git a/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php b/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php
new file mode 100644
index 0000000000000..9462996f6d2af
--- /dev/null
+++ b/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php
@@ -0,0 +1,37 @@
+
+ */
+interface CommandLoaderInterface
+{
+ /**
+ * Loads a command.
+ *
+ * @param string $name
+ *
+ * @return Command
+ *
+ * @throws CommandNotFoundException
+ */
+ public function get($name);
+
+ /**
+ * Checks if a command exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function has($name);
+
+ /**
+ * @return string[] All registered command names
+ */
+ public function getNames();
+}
diff --git a/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php b/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php
new file mode 100644
index 0000000000000..753ad0fb705c2
--- /dev/null
+++ b/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php
@@ -0,0 +1,55 @@
+
+ */
+class ContainerCommandLoader implements CommandLoaderInterface
+{
+ private $container;
+ private $commandMap;
+
+ /**
+ * @param ContainerInterface $container A container from which to load command services
+ * @param array $commandMap An array with command names as keys and service ids as values
+ */
+ public function __construct(ContainerInterface $container, array $commandMap)
+ {
+ $this->container = $container;
+ $this->commandMap = $commandMap;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
+
+ return $this->container->get($this->commandMap[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNames()
+ {
+ return array_keys($this->commandMap);
+ }
+}
diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
index d0626be16b2cf..5a323421b13e8 100644
--- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
+++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
@@ -12,9 +12,12 @@
namespace Symfony\Component\Console\DependencyInjection;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\TypedReference;
/**
* Registers console commands.
@@ -23,9 +26,20 @@
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
+ private $commandLoaderServiceId;
+ private $commandTag;
+
+ public function __construct($commandLoaderServiceId = 'console.command_loader', $commandTag = 'console.command')
+ {
+ $this->commandLoaderServiceId = $commandLoaderServiceId;
+ $this->commandTag = $commandTag;
+ }
+
public function process(ContainerBuilder $container)
{
- $commandServices = $container->findTaggedServiceIds('console.command', true);
+ $commandServices = $container->findTaggedServiceIds($this->commandTag, true);
+ $lazyCommandMap = array();
+ $lazyCommandRefs = array();
$serviceIds = array();
foreach ($commandServices as $id => $tags) {
@@ -36,21 +50,52 @@ public function process(ContainerBuilder $container)
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
- throw new InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "%s".', $id, Command::class));
+ throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
}
$commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class));
- if ($container->hasAlias($commandId) || isset($serviceIds[$commandId])) {
- $commandId = $commandId.'_'.$id;
+
+ if (!isset($tags[0]['command'])) {
+ if (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) {
+ $commandId = $commandId.'_'.$id;
+ }
+ if (!$definition->isPublic()) {
+ $container->setAlias($commandId, $id);
+ $id = $commandId;
+ }
+ $serviceIds[$commandId] = $id;
+
+ continue;
}
- if (!$definition->isPublic()) {
- $container->setAlias($commandId, $id);
- $id = $commandId;
+
+ $serviceIds[$commandId] = false;
+ $commandName = $tags[0]['command'];
+ $lazyCommandMap[$commandName] = $id;
+ $lazyCommandRefs[$id] = new TypedReference($id, $class);
+ $aliases = array();
+
+ foreach ($tags as $tag) {
+ if (!isset($tag['command'])) {
+ throw new InvalidArgumentException(sprintf('Missing "command" attribute on tag "%s" for service "%s".', $this->commandTag, $id));
+ }
+ if ($commandName !== $tag['command']) {
+ throw new InvalidArgumentException(sprintf('The "command" attribute must be the same on each "%s" tag for service "%s".', $this->commandTag, $id));
+ }
+ if (isset($tag['alias'])) {
+ $aliases[] = $tag['alias'];
+ $lazyCommandMap[$tag['alias']] = $id;
+ }
}
- $serviceIds[$commandId] = $id;
+ if ($aliases) {
+ $definition->addMethodCall('setAliases', array($aliases));
+ }
}
+ $container
+ ->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
+ ->setArguments(array(ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap));
+
$container->setParameter('console.command.ids', $serviceIds);
}
}
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index 34d9cb0c0d780..7bf76ddc8e83d 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -13,6 +13,9 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
+use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\ArgvInput;
@@ -31,6 +34,8 @@
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
class ApplicationTest extends TestCase
@@ -114,6 +119,26 @@ public function testAll()
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
}
+ public function testAllWithCommandLoader()
+ {
+ $application = new Application();
+ $commands = $application->all();
+ $this->assertInstanceOf('Symfony\\Component\\Console\\Command\\HelpCommand', $commands['help'], '->all() returns the registered commands');
+
+ $application->add(new \FooCommand());
+ $commands = $application->all('foo');
+ $this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
+
+ $application->setCommandLoader(new ContainerCommandLoader(
+ new ServiceLocator(array('foo-bar' => function () { return new \Foo1Command(); })),
+ array('foo:bar1' => 'foo-bar')
+ ));
+ $commands = $application->all('foo');
+ $this->assertCount(2, $commands, '->all() takes a namespace as its first argument');
+ $this->assertInstanceOf(\FooCommand::class, $commands['foo:bar'], '->all() returns the registered commands');
+ $this->assertInstanceOf(\Foo1Command::class, $commands['foo:bar1'], '->all() returns the registered commands');
+ }
+
public function testRegister()
{
$application = new Application();
@@ -166,6 +191,30 @@ public function testHasGet()
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input');
}
+ public function testHasGetWithCommandLoader()
+ {
+ $application = new Application();
+ $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered');
+ $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered');
+
+ $application->add($foo = new \FooCommand());
+ $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered');
+ $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
+ $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
+
+ $application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
+ 'foo-bar' => function () { return new \Foo1Command(); },
+ )), array('foo:bar1' => 'foo-bar', 'afoobar1' => 'foo-bar')));
+
+ $this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader');
+ $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns an instance by name even with command loader');
+ $this->assertEquals($foo, $application->get('afoobar'), '->get() returns an instance by alias even with command loader');
+ $this->assertTrue($application->has('foo:bar1'), '->has() returns true for commands registered in the loader');
+ $this->assertInstanceOf(\Foo1Command::class, $foo1 = $application->get('foo:bar1'), '->get() returns a command by name from the command loader');
+ $this->assertTrue($application->has('afoobar1'), '->has() returns true for commands registered in the loader');
+ $this->assertEquals($foo1, $application->get('afoobar1'), '->get() returns a command by name from the command loader');
+ }
+
public function testSilentHelp()
{
$application = new Application();
@@ -269,6 +318,20 @@ public function testFind()
$this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
}
+ public function testFindWithCommandLoader()
+ {
+ $application = new Application();
+ $application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
+ 'foo-bar' => $f = function () { return new \FooCommand(); },
+ )), array('foo:bar' => 'foo-bar')));
+
+ $this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
+ $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');
+ $this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists');
+ $this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist');
+ $this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias');
+ }
+
/**
* @dataProvider provideAmbiguousAbbreviations
*/
@@ -1362,6 +1425,35 @@ public function testCanCheckIfTerminalIsInteractive()
$this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream));
}
+ public function testRunLazyCommandService()
+ {
+ $container = new ContainerBuilder();
+ $container->addCompilerPass(new AddConsoleCommandPass());
+ $container
+ ->register('lazy-command', LazyCommand::class)
+ ->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias'))
+ ->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias2'));
+ $container->compile();
+
+ $application = new Application();
+ $application->setCommandLoader($container->get('console.command_loader'));
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+
+ $tester->run(array('command' => 'lazy:command'));
+ $this->assertSame("lazy-command called\n", $tester->getDisplay(true));
+
+ $tester->run(array('command' => 'lazy:alias'));
+ $this->assertSame("lazy-command called\n", $tester->getDisplay(true));
+
+ $tester->run(array('command' => 'lazy:alias2'));
+ $this->assertSame("lazy-command called\n", $tester->getDisplay(true));
+
+ $command = $application->get('lazy:command');
+ $this->assertSame(array('lazy:alias', 'lazy:alias2'), $command->getAliases());
+ }
+
protected function getDispatcher($skipCommand = false)
{
$dispatcher = new EventDispatcher();
@@ -1449,3 +1541,11 @@ public function __construct()
$this->setDefaultCommand($command->getName());
}
}
+
+class LazyCommand extends Command
+{
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $output->writeln('lazy-command called');
+ }
+}
diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
index 93b4721c393dd..4fcbf95753bb8 100644
--- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php
+++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
@@ -46,7 +46,7 @@ public function testConstructor()
*/
public function testCommandNameCannotBeEmpty()
{
- new Command();
+ (new Application())->add(new Command());
}
public function testSetApplication()
diff --git a/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php b/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php
new file mode 100644
index 0000000000000..78eefd24f1b3d
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Tests\CommandLoader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+
+class ContainerCommandLoaderTest extends TestCase
+{
+ public function testHas()
+ {
+ $loader = new ContainerCommandLoader(new ServiceLocator(array(
+ 'foo-service' => function () { return new Command('foo'); },
+ 'bar-service' => function () { return new Command('bar'); },
+ )), array('foo' => 'foo-service', 'bar' => 'bar-service'));
+
+ $this->assertTrue($loader->has('foo'));
+ $this->assertTrue($loader->has('bar'));
+ $this->assertFalse($loader->has('baz'));
+ }
+
+ public function testGet()
+ {
+ $loader = new ContainerCommandLoader(new ServiceLocator(array(
+ 'foo-service' => function () { return new Command('foo'); },
+ 'bar-service' => function () { return new Command('bar'); },
+ )), array('foo' => 'foo-service', 'bar' => 'bar-service'));
+
+ $this->assertInstanceOf(Command::class, $loader->get('foo'));
+ $this->assertInstanceOf(Command::class, $loader->get('bar'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
+ */
+ public function testGetUnknownCommandThrows()
+ {
+ (new ContainerCommandLoader(new ServiceLocator(array()), array()))->get('unknown');
+ }
+
+ public function testGetCommandNames()
+ {
+ $loader = new ContainerCommandLoader(new ServiceLocator(array(
+ 'foo-service' => function () { return new Command('foo'); },
+ 'bar-service' => function () { return new Command('bar'); },
+ )), array('foo' => 'foo-service', 'bar' => 'bar-service'));
+
+ $this->assertSame(array('foo', 'bar'), $loader->getNames());
+ }
+}
diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
index 0cf4631754522..6454dd63df8db 100644
--- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
+++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
@@ -12,10 +12,13 @@
namespace Symfony\Component\Console\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AddConsoleCommandPassTest extends TestCase
@@ -53,6 +56,26 @@ public function testProcess($public)
$this->assertSame(array($alias => $id), $container->getParameter('console.command.ids'));
}
+ public function testProcessRegisterLazyCommands()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register('my-command', MyCommand::class)
+ ->setPublic(false)
+ ->addTag('console.command', array('command' => 'my:command', 'alias' => 'my:alias'))
+ ;
+
+ (new AddConsoleCommandPass())->process($container);
+
+ $commandLoader = $container->getDefinition('console.command_loader');
+ $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
+
+ $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
+ $this->assertSame(array('my:command' => 'my-command', 'my:alias' => 'my-command'), $commandLoader->getArgument(1));
+ $this->assertEquals(array(array('my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class)))), $commandLocator->getArguments());
+ $this->assertSame(array('console.command.symfony_component_console_tests_dependencyinjection_mycommand' => false), $container->getParameter('console.command.ids'));
+ }
+
public function visibilityProvider()
{
return array(
@@ -72,7 +95,7 @@ public function testProcessThrowAnExceptionIfTheServiceIsAbstract()
$container->addCompilerPass(new AddConsoleCommandPass());
$definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
- $definition->addTag('console.command');
+ $definition->addTag('console.command', array('command' => 'my:command'));
$definition->setAbstract(true);
$container->setDefinition('my-command', $definition);
@@ -90,7 +113,7 @@ public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand()
$container->addCompilerPass(new AddConsoleCommandPass());
$definition = new Definition('SplObjectStorage');
- $definition->addTag('console.command');
+ $definition->addTag('console.command', array('command' => 'my:command'));
$container->setDefinition('my-command', $definition);
$container->compile();