This is the first article in a series showcasing the most important new features
introduced by Symfony 7.3, which will be released at the end of May 2025.
Contributed by
Yonel Ceruto
and Robin Chalas
in
#59340
, #59473
, #59493
and #60024
The Console component is the most popular Symfony package (excluding the pollyfil
packages), with more than 900 million downloads and 11,500 open source projects depending on it. It is also one of the oldest packages, with its first version
released in October 2011.
A typical command created with the Symfony Console looks like this:
// src/Command/CreateUserCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
protected function configure(): void
{
$this->addArgument('name', InputArgument::REQUIRED);
$this->addOption('activate', null, InputOption::VALUE_NONE);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$activate = (bool) $input->getOption('activate');
// ...
return Command::SUCCESS;
}
}
This was fine given the PHP features available at the time. However, with all the
powerful new features added to PHP in recent years (mostly attributes), we thought
we could significantly improve the DX (developer experience).
That's why in Symfony 7.3, you can define the very same command like this:
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
// ...
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __invoke(
SymfonyStyle $io, #[Argument] string $name, #[Option] bool $activate = false,
): int
{
// ...
return Command::SUCCESS;
}
}
The main changes are:
- Your command class no longer needs to extend Symfony's base
Command
class; - You don't need to override the
configure()
method to define command
options and arguments; - The values of options and arguments are available directly as variables, without
needing to call$input->getOption()
or$input->getArgument()
.
The existing #[AsCommand]
attribute was improved so you can also define the
command help there (instead of inside the configure()
method):
#[AsCommand(
name: 'app:create-user',
description: 'Adds new users to the system and optionally activates them',
help: <<%command.name% command adds a new user with the
username passed to it:
php %command.full_name% jane-doe
// ...
TXT
)]
The new #[Argument]
and #[Option]
attributes allow you to define the same
properties as the previous addArgument()
and addOption()
methods:
// add a description to explain some details about the argument
#[Argument(description: 'The user login or email address')] string $identifier,
// the command argument is called 'activate', but your code uses a different name
#[Argument(name: 'activate')] bool $isActive,
// an argument with a default value (e.g. 3) is optional
#[Argument] int $retries = 3,
// optional argument with NULL default value when it's not passed
#[Argument] ?string $name,
// an argument of type array with a default value of an empty array
#[Argument] array $ports = [],
// same for options (except that they must always define a default value)
#[Option(name: 'idle')] ?int $timeout = null,
#[Option] string $type = 'USER_TYPE',
#[Option(shortcut: 'v')] bool $verbose = false,
#[Option(description: 'User groups')] array $groups = [],
#[Option(suggestedValues: [self::class, 'getSuggestedRoles'])] array $roles = ['ROLE_USER'],
The previous way of defining commands still works, and we don't plan to deprecate
it anytime soon. However, we encourage you to adopt this new, modern, and simpler
way of creating commands.
Sponsor the Symfony project.