Skip to content

[FrameworkBundle] Command classes can't be reused in services #19001

@SenseException

Description

@SenseException

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?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions