A long awaited feature has finally landed in symfony/console 3.4.x-dev: the ability to lazy load commands. There's an article about it on Symfony's blog, but it does not explain how to setup everything. It's not very complex so here we go!

I assume you're using symfony/dependency-injection. If not, you need to create a command loader that implements the CommandLoaderInterface, which is an entirely different story.

First things first, let's create a hello command:

<?php

namespace App\Presentation\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class HelloCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln("Hello world!");
    }
}

Now let's register the command in our container config with the cunning use of tags:

services:
  console.command.hello:
    class: App\Presentation\Console\Command\HelloCommand
    tags:
    - { name: console.command, command: hello }

You need to add a compile pass to the container for the magic to happen:

<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;

$container = new ContainerBuilder();
// …
$container->addCompilerPass(new AddConsoleCommandPass);
$container->compile();

Finally, create a console file that will be used to run the CLI application. Note that in my example, the bootstrap.php script returns the container:

#!/usr/bin/env php
<?php

use Symfony\Component\Console\Application;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/* @var ContainerInterface $container */
/* @var CommandLoaderInterface $commandLoader */

$container = require __DIR__ . '/bootstrap.php';
$commandLoader = $container->get('console.command_loader');

$app = new Application();
$app->setCommandLoader($commandLoader);
$app->run();

Don't forget to change the permission to make it executable:

$ chmod +x console

The command can now be executed as follows, and should print "Hello world!":

$ ./console hello
Hello world!

tl;dr

With symfony/console 3.4.x-dev commands can be lazy loaded using a PSR-11 compatible dependency injection container. Of course, they can have their own dependencies, which are resolved just in time when they are actually executed.