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.