Console Commands as Services - Why NOT?
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.
I'm foreigner. Where I live my friends call me Maťok.