-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
During my work on creating multiple commands with similar tasks, I encountered a problem that one Command class can't be used with 2 or more services in Symfony.
My Code
I have created the command class TestCommand
without using setName
in the configure-part of the command's code:
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('<error>'.$this->getName().'</error>');
}
}
I've created 2 services using this command class:
services:
app.command.service1:
class: AppBundle\Command\TestCommand
arguments: [ "test1" ]
tags:
- { name: console.command }
app.command.service2:
class: AppBundle\Command\TestCommand
arguments: [ "test2" ]
tags:
- { name: console.command }
When I call the list of all commands, test2
will be listed and is executable, but test1
doesn't even exist.
The Problem
After some research I found that in the CompilerPass Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass
Is using the class name to create an alias, that is used for the commands to run them. It seems like this is because of BC to the non service commands, that are read and initialized in Symfony\Component\HttpKernel\Bundle\Bundle::registerCommands
.
In the Bundle class, all command classes in the Command directory of a bundle are searched with the Finder and the container is checked if there are service ids with the format of
'console.command.' . $classNameOfCommands;
that is built with the class names of the found Command classes. If the id already exists, because of the mentioned CompilerPass, it skips to the next Command.
This id format of the Command's alias has to be kept the same in the CompilerPass to stay compatible with Symfony\Component\HttpKernel\Bundle\Bundle::registerCommands
and prevent errors in there, but this makes it impossible to reuse Command classes.
Possible Solution
I want to create a PR that is going to introduce something like this in AddConsoleCommandPass
:
Before
(Line ~36)
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (!is_subclass_of($class, '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($serviceId = 'console.command.'.strtolower(str_replace('\\', '_', $class)), $id);
$serviceIds[] = $serviceId;
After
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (!is_subclass_of($class, '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));
}
$serviceId = 'console.command.'.strtolower(str_replace('\\', '_', $class));
if ($container->hasAlias($serviceId)) {
$serviceId = $serviceId . '_1'; // suffix can't be static, _1 is not the solution
}
$container->setAlias($serviceId, $id);
$serviceIds[] = $serviceId;
Before I create a PR
Before I start to fix this, I want to know if this solution is okay or if there can occur some other problems or BC breaks with this solution. The suffix also can't be static like "_1" so we may be able to use 3, 4 or more commands with the same class. I guess there should be more checked than just the existence of the alias too, but maybe I'm thinking too complex in that matter.
What's your opinion about my PR idea?