You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

183 lines
4.9 KiB

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