Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions console/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,224 @@ they are assigned to the DTO::
With this setup, when the command input is resolved, the email is lowercased
and trimmed, and roles are uppercased.

.. _console-interactive-input:

Interactive Input
-----------------

.. versionadded:: 7.4

The ``#[Ask]`` and ``#[Interact]`` attributes were introduced in Symfony 7.4.

In :ref:`invokable commands <console_creating-command>`, you can use the
:class:`Symfony\\Component\\Console\\Attribute\\Ask` attribute to prompt
users for missing values during the interactive phase, without writing a
custom ``interact()`` method.

Asking for Simple Values
~~~~~~~~~~~~~~~~~~~~~~~~

Add the ``#[Ask]`` attribute to an ``__invoke()`` parameter alongside
``#[Argument]``::

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Ask;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __invoke(
#[Argument]
#[Ask('Enter the username')]
string $username,
): int {
// ...

return Command::SUCCESS;
}
}

When the ``username`` argument is not provided, the user is prompted:

.. code-block:: terminal

$ php bin/console app:create-user

Enter the username:
> johndoe

The ``#[Ask]`` attribute supports the following options:

=============== ================================ ============ =============================================
Option Type Default Description
=============== ================================ ============ =============================================
``question`` ``string`` *(required)* The prompt text
``default`` ``string|bool|int|float|null`` ``null`` Default value if user presses Enter
``hidden`` ``bool`` ``false`` Hide input (for passwords)
``multiline`` ``bool`` ``false`` Allow newlines in the response
``trimmable`` ``bool`` ``true`` Trim whitespace from the response
``timeout`` ``int|null`` ``null`` Max seconds to wait for an answer
``normalizer`` ``callable|null`` ``null`` Normalize the input before validation
``validator`` ``callable|null`` ``null`` Custom validator
``maxAttempts`` ``int|null`` ``null`` Max retry attempts (``null`` = unlimited)
=============== ================================ ============ =============================================

Asking for Hidden Input
~~~~~~~~~~~~~~~~~~~~~~~

Use the ``hidden`` option to mask the user's input (e.g. for passwords)::

public function __invoke(
#[Argument]
#[Ask('Enter the password', hidden: true)]
string $password,
): int {
// ...
}

Asking for Confirmation
~~~~~~~~~~~~~~~~~~~~~~~

When the parameter type is ``bool``, the ``#[Ask]`` attribute automatically
uses a confirmation question::

public function __invoke(
#[Argument]
#[Ask('Do you want to activate the user?')]
bool $active,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options shouldn't be required, they should define a default value, which means interactive attributes can't be used with them.

This code will currently raise an exception.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might deserve a clear note: interactive attributes can only be used with required console arguments.

Using them with optional arguments wouldn't work either, since prompting for those values when they are missing would contradict the purpose of optional inputs.

): int {
// ...
}

.. note::

Interactive attributes (``#[Ask]``, ``#[Interact]``) can only be used
with required console arguments. Using them with options or optional
arguments is not supported and will raise an exception.

Using ``#[Ask]`` on DTO Properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``#[Ask]`` attribute also works on properties of
:ref:`input DTOs <console-input-map-input>`::

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Ask;

class CreateUserInput
{
#[Argument]
#[Ask('Enter the username')]
public string $username;

#[Argument]
#[Ask('Enter the password', hidden: true)]
public string $password;
}

Then use it in your command with ``#[MapInput]``::

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\MapInput;
use Symfony\Component\Console\Command\Command;

#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __invoke(#[MapInput] CreateUserInput $input): int
{
// use $input->username and $input->password

return Command::SUCCESS;
}
}

.. _console-interact-attribute:

Custom Interactive Logic with ``#[Interact]``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For scenarios that go beyond simple prompts, use the
:class:`Symfony\\Component\\Console\\Attribute\\Interact` attribute to mark
a method that will be called during the interactive phase::

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Ask;
use Symfony\Component\Console\Attribute\Interact;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
#[Interact]
public function prompt(SymfonyStyle $io): void
{
// custom interactive logic
}

public function __invoke(
#[Argument]
#[Ask('Enter the username')]
string $username,
): int {
// ...

return Command::SUCCESS;
}
}

The method marked with ``#[Interact]`` must be public and non-static. It
supports the same dependency-injected parameters as ``__invoke()`` (e.g.
``SymfonyStyle``, ``InputInterface``).

You can also use ``#[Interact]`` on a DTO class to handle interaction
logic related to the DTO's own properties::

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Ask;
use Symfony\Component\Console\Attribute\Interact;
use Symfony\Component\Console\Style\SymfonyStyle;

class CreateUserInput
{
#[Argument]
#[Ask('Enter the username')]
public string $username;

#[Argument]
#[Ask('Enter the password (or press Enter for a random one)', hidden: true)]
public string $password;

#[Interact]
public function prompt(SymfonyStyle $io): void
{
if (!isset($this->password)) {
$this->password = bin2hex(random_bytes(10));
$io->writeln('Password generated: '.$this->password);
}
}
}

Execution Order
~~~~~~~~~~~~~~~

When both ``#[Ask]`` and ``#[Interact]`` are used, they are executed in the
following order during the interactive phase:

#. ``#[Ask]`` on ``__invoke()`` parameters
#. ``#[Ask]`` on DTO properties
#. ``#[Interact]`` on the DTO class
#. ``#[Interact]`` on the command class

.. note::

Interactive prompts only run when the command is executed in interactive
mode. They are skipped when using the ``--no-interaction`` (``-n``) option.

Options with optional arguments
-------------------------------

Expand Down
2 changes: 2 additions & 0 deletions reference/attributes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ Command
~~~~~~~

* :ref:`Argument <console-input-arguments>`
* :ref:`Ask <console-interactive-input>`
* :ref:`AsCommand <console_creating-command>`
* :ref:`Interact <console-interact-attribute>`
* :ref:`MapInput <console-input-map-input>`
* :ref:`Option <console-input-options>`

Expand Down
Loading