ChoiceQuestion.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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\Question;
  11. use Symfony\Component\Console\Exception\InvalidArgumentException;
  12. /**
  13. * Represents a choice question.
  14. *
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. */
  17. class ChoiceQuestion extends Question
  18. {
  19. private bool $multiselect = false;
  20. private string $prompt = ' > ';
  21. private string $errorMessage = 'Value "%s" is invalid';
  22. /**
  23. * @param string $question The question to ask to the user
  24. * @param array<string|bool|int|float|\Stringable> $choices The list of available choices
  25. * @param string|bool|int|float|null $default The default answer to return
  26. */
  27. public function __construct(
  28. string $question,
  29. private array $choices,
  30. string|bool|int|float|null $default = null,
  31. ) {
  32. if (!$choices) {
  33. throw new \LogicException('Choice question must have at least 1 choice available.');
  34. }
  35. parent::__construct($question, $default);
  36. $this->setValidator($this->getDefaultValidator());
  37. $this->setAutocompleterValues($choices);
  38. }
  39. /**
  40. * @return array<string|bool|int|float|\Stringable>
  41. */
  42. public function getChoices(): array
  43. {
  44. return $this->choices;
  45. }
  46. /**
  47. * Sets multiselect option.
  48. *
  49. * When multiselect is set to true, multiple choices can be answered.
  50. *
  51. * @return $this
  52. */
  53. public function setMultiselect(bool $multiselect): static
  54. {
  55. $this->multiselect = $multiselect;
  56. $this->setValidator($this->getDefaultValidator());
  57. return $this;
  58. }
  59. /**
  60. * Returns whether the choices are multiselect.
  61. */
  62. public function isMultiselect(): bool
  63. {
  64. return $this->multiselect;
  65. }
  66. /**
  67. * Gets the prompt for choices.
  68. */
  69. public function getPrompt(): string
  70. {
  71. return $this->prompt;
  72. }
  73. /**
  74. * Sets the prompt for choices.
  75. *
  76. * @return $this
  77. */
  78. public function setPrompt(string $prompt): static
  79. {
  80. $this->prompt = $prompt;
  81. return $this;
  82. }
  83. /**
  84. * Sets the error message for invalid values.
  85. *
  86. * The error message has a string placeholder (%s) for the invalid value.
  87. *
  88. * @return $this
  89. */
  90. public function setErrorMessage(string $errorMessage): static
  91. {
  92. $this->errorMessage = $errorMessage;
  93. $this->setValidator($this->getDefaultValidator());
  94. return $this;
  95. }
  96. private function getDefaultValidator(): callable
  97. {
  98. $choices = $this->choices;
  99. $errorMessage = $this->errorMessage;
  100. $multiselect = $this->multiselect;
  101. $isAssoc = $this->isAssoc($choices);
  102. return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
  103. if ($multiselect) {
  104. // Check for a separated comma values
  105. if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) {
  106. throw new InvalidArgumentException(\sprintf($errorMessage, $selected));
  107. }
  108. $selectedChoices = explode(',', (string) $selected);
  109. } else {
  110. $selectedChoices = [$selected];
  111. }
  112. if ($this->isTrimmable()) {
  113. foreach ($selectedChoices as $k => $v) {
  114. $selectedChoices[$k] = trim((string) $v);
  115. }
  116. }
  117. $multiselectChoices = [];
  118. foreach ($selectedChoices as $value) {
  119. $results = [];
  120. foreach ($choices as $key => $choice) {
  121. if ($choice === $value) {
  122. $results[] = $key;
  123. }
  124. }
  125. if (\count($results) > 1) {
  126. throw new InvalidArgumentException(\sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
  127. }
  128. $result = array_search($value, $choices);
  129. if (!$isAssoc) {
  130. if (false !== $result) {
  131. $result = $choices[$result];
  132. } elseif (isset($choices[$value])) {
  133. $result = $choices[$value];
  134. }
  135. } elseif (false === $result && isset($choices[$value])) {
  136. $result = $value;
  137. }
  138. if (false === $result) {
  139. throw new InvalidArgumentException(\sprintf($errorMessage, $value));
  140. }
  141. // For associative choices, consistently return the key as string:
  142. $multiselectChoices[] = $isAssoc ? (string) $result : $result;
  143. }
  144. if ($multiselect) {
  145. return $multiselectChoices;
  146. }
  147. return current($multiselectChoices);
  148. };
  149. }
  150. }