Home / Symfony / New in Symfony 8.1: Improved Console Input

New in Symfony 8.1: Improved Console Input

Symfony 8.1 ships many features related to the Console component, such as
HTTP-less Symfony applications, method-based commands, and
console argument resolvers. In addition, it includes several improvements to
console command input handling.

Pasting Images As Answers


Robin Chalas
Contributed by
Robin Chalas
in
#63293

Pasting an image directly into a terminal felt like science fiction just a few
years ago. It’s now a familiar gesture, popularized by AI command-line tools
where dropping a screenshot or a picture into the prompt has become routine.
Symfony 8.1 brings that same capability to your own console commands.

When a command parameter is typed as InputFile and uses the #[Ask]
attribute, the question helper switches to file input mode:

use SymfonyComponentConsoleAttributeArgument;
use SymfonyComponentConsoleAttributeAsk;
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleInputFileInputFile;

#[AsCommand('app:analyze')]
class AnalyzeCommand
{
    public function __invoke(
        #[Argument, Ask('Provide an image (paste it or enter a path):')]
        InputFile $image,
    ): int {
        // $image comes from a pasted image or a file path
    }
}

In terminals that support the underlying graphics protocols (Ghostty, iTerm2, Kitty,
WezTerm, Konsole, Warp, and more), users can paste an image directly from their clipboard.
In other terminals, the prompt gracefully falls back to accepting a file path.

Interactive Choice Questions


Yonel Ceruto
Contributed by
Yonel Ceruto
in
#62911

Invokable commands can already prompt for missing values with the #[Ask]
attribute. Symfony 8.1 complements this with #[AskChoice], a declarative
way to ask users to select one or more values from a list:

use SymfonyComponentConsoleAttributeArgument;
use SymfonyComponentConsoleAttributeAskChoice;
use SymfonyComponentConsoleAttributeAsCommand;

#[AsCommand('app:create-user')]
class CreateUserCommand
{
    public function __invoke(
        #[Argument, AskChoice('Select a role', ['admin', 'editor', 'viewer'])]
        string $role,
    ): int {
        // $role is one of: 'admin', 'editor', 'viewer'
    }
}

The attribute adapts to the parameter type: typing it as array enables
multiple selections, and typing it as BackedEnum derives the choices
automatically from the enum cases, so you don’t have to repeat them:

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
}

public function __invoke(
    #[Argument, AskChoice('Select a status')]
    Status $status, // choices: 'active', 'inactive'
): int {
    // ...
}

Boolean Defaults for Negatable Options


Jesper Noordsij
Contributed by
Jesper Noordsij
in
#52058

Negatable options (InputOption::VALUE_NEGATABLE) accept both a flag (e.g.
--yell) and its negation (e.g. --no-yell). Symfony 8.1 lets you define a
boolean default value for them:

$this
    // ...
    ->addOption('yell', null, InputOption::VALUE_NEGATABLE, 'Whether to yell', false)
;

Objects As Default Values for Options And Arguments


Robin Chalas
Contributed by
Robin Chalas
in
#63183

Invokable commands can already map arguments and options to objects (such as
DateTimeImmutable). However, you couldn’t use an object as the default
value for such an input, which was problematic because options in invokable
commands must define a default value. Symfony 8.1 removes that limitation:

use SymfonyComponentConsoleAttributeArgument;
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleAttributeOption;

#[AsCommand('app:report')]
class ReportCommand
{
    public function __invoke(
        #[Argument] string $name,
        #[Option] DateTimeImmutable $from = new DateTimeImmutable(),
    ): int {
        // ...
    }
}

Accessing And Forwarding the Original Input


Théo FIDRY
Contributed by
Théo FIDRY
in
#57598

Some commands need to forward their own input to a child process (for example,
to parallelize work across multiple subprocesses). This was difficult to do
reliably because the parsed input merges default values and doesn’t expose the
original command-line tokens. Symfony 8.1 adds RawInputInterface
(implemented by the built-in input classes) with three methods to solve this:

  • getRawArguments() and getRawOptions() return only the arguments and
    options explicitly passed, without default values merged in;
  • unparse() turns parsed options back into their command-line form (such as
    ['--option=value', '--flag']), ready to pass to another process.
use SymfonyComponentConsoleInputRawInputInterface;
use SymfonyComponentProcessProcess;

// inside a command that receives RawInputInterface $input
$options = $input->getRawOptions();
unset($options['main-process-only-option']);

$process = new Process([
    PHP_BINARY, 'bin/console', 'my:command',
    ...$input->getRawArguments(),
    ...$input->unparse(array_keys($options)),
]);

Validating Interactive Answers


Robin Chalas
Contributed by
Robin Chalas
in
#63359

Answers entered interactively often require validation (for example, a value must
not be empty or must be a valid email address). In Symfony 8.1 you can validate
them with Symfony Validator constraints, both through the #[Ask] attribute and
the QuestionHelper. When validation fails, the user is prompted again:

use SymfonyComponentConsoleAttributeArgument;
use SymfonyComponentConsoleAttributeAsk;
use SymfonyComponentValidatorConstraints as Assert;

public function __invoke(
    #[Argument, Ask('Enter your email:', constraints: [
        new AssertNotBlank(), new AssertEmail()
    ])]
    string $email,
): int {
    // $email is guaranteed to be a non-empty, valid email
}

The same applies when working directly with Question objects, via the new
setConstraints() method:

$question = new Question('Enter a URL:');
$question->setConstraints([new AssertUrl()]);

$url = $io->askQuestion($question);

Validating Mapped Input Objects


Robin Chalas
Contributed by
Robin Chalas
in
#63714

The #[MapInput] attribute maps command arguments and options onto a typed
object. Symfony 8.1 validates that object automatically using Validator
constraints, just like #[MapRequestPayload] does for HTTP controllers:

use SymfonyComponentConsoleAttributeArgument;
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleAttributeMapInput;
use SymfonyComponentConsoleAttributeOption;
use SymfonyComponentValidatorConstraints as Assert;

class CreateUserInput
{
    #[Argument]
    #[AssertNotBlank]
    public string $name;

    #[Option]
    #[AssertEmail]
    public ?string $email = null;
}

#[AsCommand('app:create-user')]
class CreateUserCommand
{
    public function __invoke(#[MapInput] CreateUserInput $input): int
    {
        // $input is already validated
    }
}

Validation runs only when the Validator component is installed; otherwise the
constraints are ignored. On validation failure, an
InputValidationFailedException is thrown with the list of violations. You
can also restrict the checks to certain validation groups with
#[MapInput(validationGroups: ['registration'])].


Sponsor the Symfony project.
Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *