@@ -597,6 +597,224 @@ they are assigned to the DTO::
597597With this setup, when the command input is resolved, the email is lowercased
598598and 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+
600818Options with optional arguments
601819-------------------------------
602820
0 commit comments