Skip to content

Commit 389dab4

Browse files
lacatoirejaviereguiluz
authored andcommitted
[Console] Document #[Ask] and #[Interact] attributes
1 parent c05b9c1 commit 389dab4

2 files changed

Lines changed: 220 additions & 0 deletions

File tree

console/input.rst

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,224 @@ they are assigned to the DTO::
597597
With this setup, when the command input is resolved, the email is lowercased
598598
and trimmed, and roles are uppercased.
599599

600+
.. _console-interactive-input:
601+
602+
Interactive Input
603+
-----------------
604+
605+
.. versionadded:: 7.4
606+
607+
The ``#[Ask]`` and ``#[Interact]`` attributes were introduced in Symfony 7.4.
608+
609+
In :ref:`invokable commands <console_creating-command>`, you can use the
610+
:class:`Symfony\\Component\\Console\\Attribute\\Ask` attribute to prompt
611+
users for missing values during the interactive phase, without writing a
612+
custom ``interact()`` method.
613+
614+
Asking for Simple Values
615+
~~~~~~~~~~~~~~~~~~~~~~~~
616+
617+
Add the ``#[Ask]`` attribute to an ``__invoke()`` parameter alongside
618+
``#[Argument]``::
619+
620+
use Symfony\Component\Console\Attribute\Argument;
621+
use Symfony\Component\Console\Attribute\AsCommand;
622+
use Symfony\Component\Console\Attribute\Ask;
623+
use Symfony\Component\Console\Command\Command;
624+
625+
#[AsCommand(name: 'app:create-user')]
626+
class CreateUserCommand
627+
{
628+
public function __invoke(
629+
#[Argument]
630+
#[Ask('Enter the username')]
631+
string $username,
632+
): int {
633+
// ...
634+
635+
return Command::SUCCESS;
636+
}
637+
}
638+
639+
When the ``username`` argument is not provided, the user is prompted:
640+
641+
.. code-block:: terminal
642+
643+
$ php bin/console app:create-user
644+
645+
Enter the username:
646+
> johndoe
647+
648+
The ``#[Ask]`` attribute supports the following options:
649+
650+
=============== ================================ ============ =============================================
651+
Option Type Default Description
652+
=============== ================================ ============ =============================================
653+
``question`` ``string`` *(required)* The prompt text
654+
``default`` ``string|bool|int|float|null`` ``null`` Default value if user presses Enter
655+
``hidden`` ``bool`` ``false`` Hide input (for passwords)
656+
``multiline`` ``bool`` ``false`` Allow newlines in the response
657+
``trimmable`` ``bool`` ``true`` Trim whitespace from the response
658+
``timeout`` ``int|null`` ``null`` Max seconds to wait for an answer
659+
``normalizer`` ``callable|null`` ``null`` Normalize the input before validation
660+
``validator`` ``callable|null`` ``null`` Custom validator
661+
``maxAttempts`` ``int|null`` ``null`` Max retry attempts (``null`` = unlimited)
662+
=============== ================================ ============ =============================================
663+
664+
Asking for Hidden Input
665+
~~~~~~~~~~~~~~~~~~~~~~~
666+
667+
Use the ``hidden`` option to mask the user's input (e.g. for passwords)::
668+
669+
public function __invoke(
670+
#[Argument]
671+
#[Ask('Enter the password', hidden: true)]
672+
string $password,
673+
): int {
674+
// ...
675+
}
676+
677+
Asking for Confirmation
678+
~~~~~~~~~~~~~~~~~~~~~~~
679+
680+
When the parameter type is ``bool``, the ``#[Ask]`` attribute automatically
681+
uses a confirmation question::
682+
683+
public function __invoke(
684+
#[Argument]
685+
#[Ask('Do you want to activate the user?')]
686+
bool $active,
687+
): int {
688+
// ...
689+
}
690+
691+
.. note::
692+
693+
Interactive attributes (``#[Ask]``, ``#[Interact]``) can only be used
694+
with required console arguments. Using them with options or optional
695+
arguments is not supported and will raise an exception.
696+
697+
Using ``#[Ask]`` on DTO Properties
698+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
699+
700+
The ``#[Ask]`` attribute also works on properties of
701+
:ref:`input DTOs <console-input-map-input>`::
702+
703+
use Symfony\Component\Console\Attribute\Argument;
704+
use Symfony\Component\Console\Attribute\Ask;
705+
706+
class CreateUserInput
707+
{
708+
#[Argument]
709+
#[Ask('Enter the username')]
710+
public string $username;
711+
712+
#[Argument]
713+
#[Ask('Enter the password', hidden: true)]
714+
public string $password;
715+
}
716+
717+
Then use it in your command with ``#[MapInput]``::
718+
719+
use Symfony\Component\Console\Attribute\AsCommand;
720+
use Symfony\Component\Console\Attribute\MapInput;
721+
use Symfony\Component\Console\Command\Command;
722+
723+
#[AsCommand(name: 'app:create-user')]
724+
class CreateUserCommand
725+
{
726+
public function __invoke(#[MapInput] CreateUserInput $input): int
727+
{
728+
// use $input->username and $input->password
729+
730+
return Command::SUCCESS;
731+
}
732+
}
733+
734+
.. _console-interact-attribute:
735+
736+
Custom Interactive Logic with ``#[Interact]``
737+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
738+
739+
For scenarios that go beyond simple prompts, use the
740+
:class:`Symfony\\Component\\Console\\Attribute\\Interact` attribute to mark
741+
a method that will be called during the interactive phase::
742+
743+
use Symfony\Component\Console\Attribute\Argument;
744+
use Symfony\Component\Console\Attribute\AsCommand;
745+
use Symfony\Component\Console\Attribute\Ask;
746+
use Symfony\Component\Console\Attribute\Interact;
747+
use Symfony\Component\Console\Command\Command;
748+
use Symfony\Component\Console\Style\SymfonyStyle;
749+
750+
#[AsCommand(name: 'app:create-user')]
751+
class CreateUserCommand
752+
{
753+
#[Interact]
754+
public function prompt(SymfonyStyle $io): void
755+
{
756+
// custom interactive logic
757+
}
758+
759+
public function __invoke(
760+
#[Argument]
761+
#[Ask('Enter the username')]
762+
string $username,
763+
): int {
764+
// ...
765+
766+
return Command::SUCCESS;
767+
}
768+
}
769+
770+
The method marked with ``#[Interact]`` must be public and non-static. It
771+
supports the same dependency-injected parameters as ``__invoke()`` (e.g.
772+
``SymfonyStyle``, ``InputInterface``).
773+
774+
You can also use ``#[Interact]`` on a DTO class to handle interaction
775+
logic related to the DTO's own properties::
776+
777+
use Symfony\Component\Console\Attribute\Argument;
778+
use Symfony\Component\Console\Attribute\Ask;
779+
use Symfony\Component\Console\Attribute\Interact;
780+
use Symfony\Component\Console\Style\SymfonyStyle;
781+
782+
class CreateUserInput
783+
{
784+
#[Argument]
785+
#[Ask('Enter the username')]
786+
public string $username;
787+
788+
#[Argument]
789+
#[Ask('Enter the password (or press Enter for a random one)', hidden: true)]
790+
public string $password;
791+
792+
#[Interact]
793+
public function prompt(SymfonyStyle $io): void
794+
{
795+
if (!isset($this->password)) {
796+
$this->password = bin2hex(random_bytes(10));
797+
$io->writeln('Password generated: '.$this->password);
798+
}
799+
}
800+
}
801+
802+
Execution Order
803+
~~~~~~~~~~~~~~~
804+
805+
When both ``#[Ask]`` and ``#[Interact]`` are used, they are executed in the
806+
following order during the interactive phase:
807+
808+
#. ``#[Ask]`` on ``__invoke()`` parameters
809+
#. ``#[Ask]`` on DTO properties
810+
#. ``#[Interact]`` on the DTO class
811+
#. ``#[Interact]`` on the command class
812+
813+
.. note::
814+
815+
Interactive prompts only run when the command is executed in interactive
816+
mode. They are skipped when using the ``--no-interaction`` (``-n``) option.
817+
600818
Options with optional arguments
601819
-------------------------------
602820

reference/attributes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Command
1515
~~~~~~~
1616

1717
* :ref:`Argument <console-input-arguments>`
18+
* :ref:`Ask <console-interactive-input>`
1819
* :ref:`AsCommand <console_creating-command>`
20+
* :ref:`Interact <console-interact-attribute>`
1921
* :ref:`MapInput <console-input-map-input>`
2022
* :ref:`Option <console-input-options>`
2123

0 commit comments

Comments
 (0)