How to improve your Symfony commands

November 17, 2020

When we develop a Symfony project, we inherit a lot of default commands. For example, if we create a new project starting from symfony/website-skeleton, we already have more than 80 commands. We can prefix all our custom commands with app: so that they don’t get mixed up, but the display remains somewhat overloaded.

I’m going to present some of the tricks I configure on most of my Symfony projects.

The main one consists of creating a new default command that will allow us to filter what is displayed via the bin/console instruction so that there are only our custom commands. Don’t worry! The bin/console list behaviour will not change to let us see all available commands.

Initialization

To illustrate this post, we will generate a new Symfony project using website-skeleton.

1composer create-project symfony/website-skeleton sandbox
2cd sandbox
3composer install --optimize-autoloader

Default command

Let’s start by creating our new default command: src/Command/DefaultCommand.php. In addition to the usual elements, we explicitly state that this is a hidden command that will not appear in the lists.

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace App\Command;
 6
 7use Symfony\Component\Console\Application;
 8use Symfony\Component\Console\Command\Command;
 9use Symfony\Component\Console\Input\ArrayInput;
10use Symfony\Component\Console\Input\InputInterface;
11use Symfony\Component\Console\Output\OutputInterface;
12
13class DefaultCommand extends Command
14{
15    /**
16     * {@inheritdoc}
17     */
18    protected static $defaultName = 'app:default';
19
20    /**
21     * {@inheritdoc}
22     */
23    protected function configure(): void
24    {
25        $this->setDescription('Wrapper of the default "list" command');
26        $this->setHidden(true);
27    }
28
29    # ...
30}

The execute method will probably be a bit different from the one you are used to writing since we will use an already existing command: list. Therefore, we are also going to add an extra parameter to it, the one that allows us to display only our custom commands.

 1    /**
 2     * {@inheritdoc}
 3     */
 4    protected function execute(InputInterface $input, OutputInterface $output): int
 5    {
 6        /** @var Application $application */
 7        $application = $this->getApplication();
 8
 9        $command = $application->find('list');
10        $arguments = ['namespace' => 'app'];
11
12        $listInput = new ArrayInput($arguments);
13
14        return $command->run($listInput, $output);
15    }

Custom application

To invoke this new command when you write bin/console, you will need to configure the application default command. There are two ways to achieve this.

You can edit the bin/console file to add the default command statement.

1$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
2$application = new Application($kernel);
3+ $application->setDefaultCommand(\App\Command\DefaultCommand::getDefaultName());
4$application->run($input);

You can also create your own Application class and instantiate it instead of the Symfony class, still within the bin/console file.

1- use Symfony\Bundle\FrameworkBundle\Console\Application;
2+ use App\Application;
 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace App;
 6
 7class Application extends \Symfony\Component\Console\Application
 8{
 9    /**
10     * {@inheritdoc}
11     *
12     * @param Kernel $kernel
13     */
14    public function __construct(KernelInterface $kernel)
15    {
16        parent::__construct($kernel);
17
18        if ($defaultName = DefaultCommand::getDefaultName()) {
19            $this->setDefaultCommand($defaultName);
20        }
21    }
22}

I prefer this second option. It may be more verbose, but it allows for more customizations in addition to the default command.

  • Adding a custom header before the commands.
  • The configuration of a custom name to replace Symfony.
  • The configuration of a custom version to replace the Symfony version.

Here is a complete example with all these customizations.

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace App;
 6
 7use App\Command\DefaultCommand;
 8use Symfony\Bundle\FrameworkBundle\Console\Application as SymfonyApplication;
 9use Symfony\Component\HttpKernel\KernelInterface;
10
11class Application extends SymfonyApplication
12{
13    public const CONSOLE_LOGO = <<<'ASCII'
14  ___                _   _    _           
15 / __| ___ _ __  ___| |_| |_ (_)_ _  __ _ 
16 \__ \/ _ \ '  \/ -_)  _| ' \| | ' \/ _` |
17 |___/\___/_|_|_\___|\__|_||_|_|_||_\__, |
18                                    |___/ 
19
20
21ASCII;
22
23    public const CONSOLE_NAME = 'Something';
24
25    /**
26     * {@inheritdoc}
27     *
28     * @param Kernel $kernel
29     */
30    public function __construct(KernelInterface $kernel)
31    {
32        parent::__construct($kernel);
33
34        if ($defaultName = DefaultCommand::getDefaultName()) {
35            $this->setDefaultCommand($defaultName);
36        }
37    }
38
39    /**
40     * {@inheritdoc}
41     */
42    public function getHelp(): string
43    {
44        return self::CONSOLE_LOGO.parent::getHelp();
45    }
46
47    /**
48     * {@inheritdoc}
49     */
50    public function getName(): string
51    {
52        return self::CONSOLE_NAME;
53    }
54
55    /**
56     * {@inheritdoc}
57     */
58    public function getVersion(): string
59    {
60        return 'X.Y.Z';
61    }
62}

Conclusion

It is not for nothing that Symfony Console is the most downloaded Symfony component. It is both powerful and straightforward to configure. I hope that this article will have taught you at least a little bit about it.