Best Practice for Symfony Console in Nette
Filip ProcházkaIf you use Symfony\Console in Nette, you will be probably familiar with php index.php command
approach.
It has been obsolete since Nette 2.3, and we should all migrate to its successor.
This blog post will show you why and how.
Running console through the www/index.php
was introduced in Kdyby\Console by me (Filip Procházka) and the practice is now deprecated. This article shows why it was introduced, why it is deprecated and how to use Symfony\Console more elegantly.
Why was running through index.php introduced
For example, you might have a mailer service that handles rendering of a template for the email and then sending it. And a regular email probably has some links in it. That is what the LinkGenerator
is for, which was introduced in Nette 2.3.
Before Nette 2.3 the simplest way to solve this was to fetch the current instance of UI\presenter
from Nette\Application\Application
, pass it to the template you're trying to render and only then the URL generating was working. And to have the UI\presenter
in cli
, you had to execute Nette\Application\Application
that would create the presenter so you can generate the URL.
In short, every time you call php www/index.php command
, Kdyby\Console is
- taking over the Nette routing with
CliRoute
CliRoute
creates an application request forKdybyModule\CliPresenter
CliPresenter
calls Symfony Application and passes your command name and arguments
This guarantees you'll always have an UI\Presenter
in Nette\Application\Application
that can be used for generating URLs.
Missing Http\Url problem
Having the LinkGenerator
(or UI\Presenter
in the past) available is not enough for generating URLs. It requires an Http\Url
instance that is by default fetched from the Http\IRequest
service. This is solved by Kdyby too. You can just configure an URL and Kdyby will rewrite the Http\IRequest
service and provide a fake instance with the URL configured.
console:
url: https://pehapkari.cz/
Simplifying the execution
Executing the console through Nette\Application\Application
is no longer necessary since we have the LinkGenerator
. So how can we make this more elegant? Since we're integrating Symfony\Console, a logical approach is to look what Symfony thinks is the best practice.
Symfony has a bin/console
script with contents similar to the following snippet which is sufficient for Nette
#!/usr/bin/env php
<?php declare(strict_types=1);
/** @var \Nette\DI\Container $container */
$container = require __DIR__ . '/../app/bootstrap.php';
/** @var \Symfony\Component\Console\Application $consoleApplication */
$console = $container->getByType(Symfony\Component\Console\Application::class);
exit($console->run());
that we can execute by typing
php bin/console help
And if we make it executable with
chmod +x bin/console
we can even drop the php
when executing it
bin/console help
This removes the extra layers of abstractions that are no longer needed thanks to the LinkGenerator
. But, having the console.url
option is still necessary to correctly generate URLs.
Utilizing decorator extension
With Nette 2.3 a new DecoratorExtension
was introduced that greatly simplifies command registration. We can use it, to find all services that have a given type and give them all a tag or some common setup calls.
With the extension this config
# app/config/config.neon
services:
-
class: App\Console\FirstCommand
tags: [kdyby.console.command]
-
class: App\Console\SecondCommand
tags: [kdyby.console.command]
-
class: App\Console\ThirdCommand
tags: [kdyby.console.command]
can be simplified to
# app/config/config.neon
services:
- App\Console\FirstCommand
- App\Console\SecondCommand
- App\Console\ThirdCommand
decorator:
Symfony\Component\Console\Command\Command:
tags: [kdyby.console.command]
Nette extensions allow to search for a given type in compile-time, and therefore Kdyby\Console (and any other extension) could just work without tags, but tags are a predictable solution to marking services for special processing. If you want to just tag everything for processing, it's better if you do it explicitly yourself using the DecoratorExtension
.
Auto-complete support
Another added benefit of switching to bin/console
is automatic support of Symfony Console by zsh
. The default zsh Symfony Console integration is invoked when you execute bin/console
in your terminal and auto-completes the commands and options. Thanks Klára, for pointing this out!
Example of the refactoring
If you'd like to see a real-life code, Tomáš Votruba made PR on Github to Eventigo.cz recently.
Conclusion
This article was mainly about Kdyby\Console and its history, but to be fair, I have to mention Contributte\Console by Milan Felix Šulc that actually prompted the update in Kdyby\Console documentation.
Also, a side-note to whoever is using Kdyby\Doctrine - it is planned to make Kdyby\Console optional which will remove the vendor lock-in for Kdyby\Console, so you can choose what integration to use. This will allow you not to use Symfony\Console at all, or replace Kdyby\Console with Contributte\Console if you decide to do so.
There is always a better way to approach things. Please, share with us how you're using Symfony\Console in the comments.