Argument.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Attribute;
  11. use Symfony\Component\Console\Completion\CompletionInput;
  12. use Symfony\Component\Console\Completion\Suggestion;
  13. use Symfony\Component\Console\Exception\LogicException;
  14. use Symfony\Component\Console\Input\InputArgument;
  15. use Symfony\Component\Console\Input\InputInterface;
  16. use Symfony\Component\String\UnicodeString;
  17. #[\Attribute(\Attribute::TARGET_PARAMETER)]
  18. class Argument
  19. {
  20. private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];
  21. private string|bool|int|float|array|null $default = null;
  22. private array|\Closure $suggestedValues;
  23. private ?int $mode = null;
  24. private string $function = '';
  25. /**
  26. * Represents a console command <argument> definition.
  27. *
  28. * If unset, the `name` value will be inferred from the parameter definition.
  29. *
  30. * @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
  31. */
  32. public function __construct(
  33. public string $description = '',
  34. public string $name = '',
  35. array|callable $suggestedValues = [],
  36. ) {
  37. $this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
  38. }
  39. /**
  40. * @internal
  41. */
  42. public static function tryFrom(\ReflectionParameter $parameter): ?self
  43. {
  44. /** @var self $self */
  45. if (null === $self = ($parameter->getAttributes(self::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)?->newInstance()) {
  46. return null;
  47. }
  48. if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) {
  49. $self->function = $function->class.'::'.$function->name;
  50. } else {
  51. $self->function = $function->name;
  52. }
  53. $type = $parameter->getType();
  54. $name = $parameter->getName();
  55. if (!$type instanceof \ReflectionNamedType) {
  56. throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $name, $self->function));
  57. }
  58. $parameterTypeName = $type->getName();
  59. if (!\in_array($parameterTypeName, self::ALLOWED_TYPES, true)) {
  60. throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command argument. Only "%s" types are allowed.', $parameterTypeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
  61. }
  62. if (!$self->name) {
  63. $self->name = (new UnicodeString($name))->kebab();
  64. }
  65. $self->default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
  66. $self->mode = $parameter->isDefaultValueAvailable() || $parameter->allowsNull() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
  67. if ('array' === $parameterTypeName) {
  68. $self->mode |= InputArgument::IS_ARRAY;
  69. }
  70. if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $parameter->getDeclaringFunction()->getClosureThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) {
  71. $self->suggestedValues = [$instance, $self->suggestedValues[1]];
  72. }
  73. return $self;
  74. }
  75. /**
  76. * @internal
  77. */
  78. public function toInputArgument(): InputArgument
  79. {
  80. $suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
  81. return new InputArgument($this->name, $this->mode, $this->description, $this->default, $suggestedValues);
  82. }
  83. /**
  84. * @internal
  85. */
  86. public function resolveValue(InputInterface $input): mixed
  87. {
  88. return $input->getArgument($this->name);
  89. }
  90. }