Matok's PHP Blog

Console Commands as Services - Why NOT?

article image

Original article I have read is here by Matthias Noback. Thus there is useful information but there are parts where author is terribly wrong. I don't want react to each paragraph rather express my opinion about main problem that is single point of failure.

Good way: ContainerAwareCommand

You can write your commands like this and get a service from container (in this example matok_media.repository.media) when you need it.

class HardDeleteMediaCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('matok:media:hard-delete')
            ->setDescription('Permanet remove of assets')
            ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $repository = $this->getContainer()->get('matok_media.repository.media');
        // ...
    }
}

Bad way: Command as service

class StupidCommand extends Command
{
    /** @var MediaRepository  */
    private $repository;

    public function __construct(MediaRepository $repository)
    {
        $this->repository = $repository;
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('matok:media:hard-delete-wrong-way')
            ->setDescription('Permanet remove of assets')
            ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
         // ...
    }
}

... and service definition...

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="stupid_command" class="Matok\Bundle\MediaBundle\Command\StupidCommand">
            <argument type="service" id="matok_media.repository.media" />
            <tag name="console.command" />
        </service>
    </services>
</container>

There is no difference

Second variant (command defined as service) is doing same job, but has more code. Both files (php & xml) are needed to everything run. More code is worse readable, worse maintainable - you don't want to change dependencies in your application with 20 commands! It's hell without kind of Symfony plugin in your IDE. Better way is access to service container when you need it and get service when you need it. Both variants are coupled to symfony service container. In first case by extending ContainerAwareCommand and in second case in XML definition.

ContainerAwareCommand: Yes, use it!

Why I use ContainerAwareCommand in my application is something called Single point of failure. What if you add new command and all commands stop working? With commands as services it could happened easily. Look at this simple code that demonstrates some expensive (time/resources) construction of others object that are injected into the constructor.

class ExpensiveCommand
{
    public function __construct()
    {
        sleep(3);
        echo 'I was sleeping for a while...';
    }
}
What happened when I type: $ php bin/console debug:router
I was sleeping for a while... 
 ---------------------------------------------------- -------- -------- ------ ---------------------
  Name                                                 Method   Scheme   Host   Path
 ---------------------------------------------------- -------- -------- ------ ---------------------
  _wdt                                                 ANY      ANY      ANY    /_wdt/{token}
  _profiler_home                                       ANY      ANY      ANY    /_profiler/

Yes, Symfony is calling a constructor of each command even if run want to run something something else. What happened if constructor in new command throws an Exception that is not internally caught? All command ends up with this exception.

Final word

One Command to rule them all, One Command to find them, One Command to bring them all and in the darkness bind them

Someone can say that this is matter of preferences... I don't agree. Before Symfony 3.4 that introduce lazy commands it's Russian roulette with your application. By using ContainerAwareCommand you can prevent a lot of problems.


If you like this article then mark it as helpful to let others know it's worth to read. Otherwise leave me a feedback/comment and we can talk about it.

I'm foreigner. Where I live my friends call me Maťok.


Comments - Coming Soon