diff --git a/components/console/console_arguments.rst b/components/console/console_arguments.rst index da538ac78f1..4ced8fce033 100644 --- a/components/console/console_arguments.rst +++ b/components/console/console_arguments.rst @@ -11,6 +11,26 @@ Have a look at the following command that has three options:: namespace Acme\Console\Command; + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Attribute\Option; + + #[AsCommand(name: 'demo:args', description: 'Describe args behaviors')] + class DemoArgsCommand + { + public function __invoke( + #[Option(shortcut: 'f')] bool $foo = false, + #[Option(shortcut: 'b')] string $bar = '', + #[Option(shortcut: 'c')] string|bool $cat = false, + ): int { + // ... + } + } + +This example uses :ref:`invokable commands ` with the +``#[Option]`` attribute. If you prefer the classic approach:: + + namespace Acme\Console\Command; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -87,4 +107,39 @@ Input ``bar`` ``cat`` ``arg`` ``-b Hello -c World`` ``"Hello"`` ``"World"`` ``null`` ============================== ================= =========== =========== +.. _console-option-constraints: + +Option Attribute Constraints +---------------------------- + +When using the ``#[Option]`` attribute in invokable commands, the following +rules are enforced to ensure consistent behavior: + +* Options **must always have a default value**. Unlike arguments, options cannot + be required since users may simply not provide them; +* Nullable bool options (``?bool``) cannot have a ``true`` or ``false`` default. + Use ``null`` as the default to enable negatable behavior; +* Nullable non-bool options (e.g. ``?string``) must have ``null`` as the default value; +* Union types are only allowed for ``string|bool``, ``int|bool``, and ``float|bool``, + and must have ``false`` as the default value. + +Examples of valid option definitions:: + + #[Option] bool $verbose = false // VALUE_NONE + #[Option] bool $colors = true // VALUE_NEGATABLE (--colors or --no-colors) + #[Option] ?bool $debug = null // VALUE_NEGATABLE (--debug or --no-debug) + #[Option] string $format = 'json' // VALUE_REQUIRED + #[Option] ?string $filter = null // VALUE_REQUIRED (optional value) + #[Option] int $limit = 10 // VALUE_REQUIRED + #[Option] array $roles = [] // VALUE_IS_ARRAY + #[Option] string|bool $output = false // VALUE_OPTIONAL (--output or --output=file.txt) + +Examples of **invalid** option definitions:: + + #[Option] string $format // ERROR: no default value + #[Option] ?bool $debug = true // ERROR: nullable bool with true default + #[Option] ?string $filter = 'default' // ERROR: nullable with non-null default + #[Option] string|bool $output = true // ERROR: union type with true default + #[Option] array|bool $items = false // ERROR: unsupported union type + .. _docopt: http://docopt.org/ diff --git a/console/input.rst b/console/input.rst index d5b6e4881bb..2ab7443cdac 100644 --- a/console/input.rst +++ b/console/input.rst @@ -10,7 +10,65 @@ Using Command Arguments Arguments are the strings - separated by spaces - that come after the command name itself. They are ordered, and can be optional -or required. For example, to add an optional ``last_name`` argument to the command +or required. + +Using Arguments in Invokable Commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In :ref:`invokable commands `, use the +:class:`Symfony\\Component\\Console\\Attribute\\Argument` attribute +to define arguments directly in the ``__invoke()`` method parameters:: + + // ... + use Symfony\Component\Console\Attribute\Argument; + use Symfony\Component\Console\Attribute\AsCommand; + + #[AsCommand(name: 'app:greet')] + class GreetCommand + { + public function __invoke( + // required argument (no default value) + #[Argument] + string $name, + + // optional argument (has default value) + #[Argument] + string $lastName = '', + ): int { + // ... + } + } + +The ``Argument`` attribute accepts the following parameters: + +``description`` + A description of the argument shown when displaying the command help. + For example: ``#[Argument(description: 'Your username')]``. + +``name`` + The name of the argument (by default, the parameter name converted to ``kebab-case``). + For example: ``#[Argument(name: 'user-name')]``. + +``suggestedValues`` + An array or a callable that provides :ref:`suggested values for the argument `. + For example: ``#[Argument(suggestedValues: ['Alice', 'Bob'])]``. + +.. versionadded:: 7.3 + + The ``#[Argument]`` and ``#[Option]`` attributes were introduced in Symfony 7.3. + +The argument mode (required, optional, array) is inferred from the parameter type: + +* **Required**: Parameters without a default value and not nullable (e.g. ``string $name``); +* **Optional**: Parameters with a default value (e.g. ``string $name = ''`` or ``?string $name = null``); +* **Array**: Parameters with the ``array`` type (e.g. ``array $names = []``); + +Using the Classic configure() Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you prefer the classic approach, or need to extend the ``Command`` class, +you can use the ``addArgument()`` method in the ``configure()`` method. +For example, to add an optional ``last_name`` argument to the command and make the ``name`` argument required:: // ... @@ -125,6 +183,97 @@ order) and are specified with two dashes (e.g. ``--yell``). Options are *always* optional, and can be setup to accept a value (e.g. ``--dir=src``) or as a boolean flag without a value (e.g. ``--yell``). +Using Options in Invokable Commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In :ref:`invokable commands `, use the +:class:`Symfony\\Component\\Console\\Attribute\\Option` attribute +to define options directly in the ``__invoke()`` method parameters:: + + // ... + use Symfony\Component\Console\Attribute\Argument; + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Attribute\Option; + + #[AsCommand(name: 'app:greet')] + class GreetCommand + { + public function __invoke( + #[Argument] + string $name, + + // option that accepts a value (--iterations=5) + #[Option] + int $iterations = 1, + + // boolean flag (--yell) + #[Option] + bool $yell = false, + ): int { + // ... + } + } + +The ``Option`` attribute accepts the following parameters: + +``description`` + A description of the option shown when displaying the command help. + For example: ``#[Option(description: 'Number of iterations')]``. + +``name`` + The name of the option (by default, the parameter name converted to ``kebab-case``). + For example: ``#[Option(name: 'max-retries')]``. + +``shortcut`` + A one-letter shortcut that can be used instead of the full option name. + For example: ``#[Option(shortcut: 'i')]`` allows using ``-i`` instead of ``--iterations``. + +``suggestedValues`` + An array or a callable that provides :ref:`suggested values for the option `. + For example: ``#[Option(suggestedValues: ['low', 'medium', 'high'])]``. + +The option mode is inferred from the parameter type and default value: + +* **Boolean flag** (``VALUE_NONE``): ``bool`` type with default ``false``. + Usage: ``--yell`` sets the value to ``true``:: + + #[Option] bool $yell = false + +* **Negatable flag** (``VALUE_NEGATABLE``): ``bool`` type with default ``true`` or + nullable ``?bool`` with default ``null``. Usage: ``--yell`` or ``--no-yell``:: + + #[Option] bool $yell = true + #[Option] ?bool $yell = null + +* **Value required** (``VALUE_REQUIRED``): ``string``, ``int`` or ``float`` types:: + + #[Option] string $format = 'json' + #[Option] int $limit = 10 + #[Option] ?string $filter = null + +* **Array of values** (``VALUE_IS_ARRAY``): ``array`` type. + Usage: ``--role=ADMIN --role=USER``:: + + #[Option(description: 'User roles')] array $roles = [] + #[Option] ?array $tags = null + +* **Value optional** (``VALUE_OPTIONAL``): Union types ``string|bool``, ``int|bool``, + or ``float|bool`` with default ``false``. Usage: ``--output`` (returns ``true``) + or ``--output=file.txt`` (returns ``'file.txt'``):: + + #[Option] string|bool $output = false + +.. seealso:: + + The ``#[Option]`` attribute enforces validation rules on type and default + value combinations. See :ref:`Option Attribute Constraints ` + for the complete list of rules and examples. + +Using the Classic addOption() Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you prefer the classic approach, or need to extend the ``Command`` class, +you can use the ``addOption()`` method in the ``configure()`` method. For example, add a new option to the command that can be used to specify how many times in a row the message should be printed:: @@ -347,6 +496,8 @@ command without having to worry about the number of arguments or options:: The :method:`Symfony\\Component\\Console\\Input\\ArgvInput::getRawTokens` method was introduced in Symfony 7.1. +.. _console-input-completion: + Adding Argument/Option Value Completion --------------------------------------- @@ -356,7 +507,57 @@ can also implement value completion for the input in your commands. For instance, you may want to complete all usernames from the database in the ``name`` argument of your greet command. -To achieve this, use the 5th argument of ``addArgument()`` or the 6th argument of ``addOption()``:: +Using Completion with Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the ``#[Argument]`` or ``#[Option]`` attributes in invokable commands, +use the ``suggestedValues`` parameter to provide completion values:: + + use Symfony\Component\Console\Attribute\Argument; + use Symfony\Component\Console\Attribute\Option; + use Symfony\Component\Console\Completion\CompletionInput; + + public function __invoke( + // static list of suggested values + #[Argument(suggestedValues: ['Alice', 'Bob', 'Charlie'])] + string $name, + + // dynamic values via a callable (method reference) + #[Option(suggestedValues: [self::class, 'suggestFormats'])] + string $format = 'json', + ): int { + // ... + } + + public static function suggestFormats(CompletionInput $input): array + { + return ['json', 'xml', 'csv']; + } + +.. note:: + + You can remove the ``static`` keyword from the suggestion method to access + instance properties. In that case, the command will call the method + non-statically, allowing you to return dynamic values based on services + injected through the constructor:: + + public function __construct( + private UserRepository $userRepository, + ) { + } + + // ... + + public function suggestUsers(CompletionInput $input): array + { + return $this->userRepository->findAllUsernames(); + } + +Using Completion with addArgument()/addOption() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the classic ``configure()`` method, use the 5th argument +of ``addArgument()`` or the 6th argument of ``addOption()``:: // ... use Symfony\Component\Console\Completion\CompletionInput;