Symfony 8.2 New Published on

Docker-style sub-commands

tenant --name=acme users list: the group carries the option once, each sub-command reads it from the command chain.

Topics covered: Console

New in Symfony 8.2 (in development). Console commands are organized in namespaces separated by colons (cache:clear). Symfony 8.2 also accepts the Docker or Git form, with spaces: bin/console cache clear runs cache:clear. Above all, each level reads its own options.

Before

Each command of a group declares the same option again:

bin/console tenant:users:list --tenant=acme
bin/console tenant:users:count --tenant=acme

Since Symfony 8.2

The group declares the option once, in its #[AsCommand], and the sub-commands read it from the chain of resolved commands:

bin/console tenant --name=acme users list
#[AsCommand('tenant', options: [
    new InputOption('name', null, InputOption::VALUE_REQUIRED, 'The tenant name'),
])]
class TenantCommands
{
    #[AsCommand('users:list')]
    public function listUsers(CommandChain $chain, OutputInterface $output): int
    {
        $tenant = $chain->getInput('tenant')?->getOption('name');
        // …
    }
}

A sub-command doesn't inherit its parent's options: it reads them with CommandChain::getInput(). The parent is only part of the chain in the form with spaces. Run as tenant:users:list, the command doesn't find it: getInput('tenant') returns null.

Your task

In TenantCommands, replace the --tenant option of the two sub-commands with a --name option declared once on the tenant group. Without the group (tenant:users:list), the tenant stays default.

To try it in the console: bin/console tenant --name=acme users list, then bin/console tenant, which lists its sub-commands.

Where the feature comes from: the original pull request.