1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Console\Question; 13 14use Symfony\Component\Console\Exception\InvalidArgumentException; 15 16/** 17 * Represents a choice question. 18 * 19 * @author Fabien Potencier <fabien@symfony.com> 20 */ 21class ChoiceQuestion extends Question 22{ 23 private $choices; 24 private $multiselect = false; 25 private $prompt = ' > '; 26 private $errorMessage = 'Value "%s" is invalid'; 27 28 /** 29 * @param string $question The question to ask to the user 30 * @param array $choices The list of available choices 31 * @param mixed $default The default answer to return 32 */ 33 public function __construct(string $question, array $choices, $default = null) 34 { 35 if (!$choices) { 36 throw new \LogicException('Choice question must have at least 1 choice available.'); 37 } 38 39 parent::__construct($question, $default); 40 41 $this->choices = $choices; 42 $this->setValidator($this->getDefaultValidator()); 43 $this->setAutocompleterValues($choices); 44 } 45 46 /** 47 * Returns available choices. 48 * 49 * @return array 50 */ 51 public function getChoices() 52 { 53 return $this->choices; 54 } 55 56 /** 57 * Sets multiselect option. 58 * 59 * When multiselect is set to true, multiple choices can be answered. 60 * 61 * @param bool $multiselect 62 * 63 * @return $this 64 */ 65 public function setMultiselect($multiselect) 66 { 67 $this->multiselect = $multiselect; 68 $this->setValidator($this->getDefaultValidator()); 69 70 return $this; 71 } 72 73 /** 74 * Returns whether the choices are multiselect. 75 * 76 * @return bool 77 */ 78 public function isMultiselect() 79 { 80 return $this->multiselect; 81 } 82 83 /** 84 * Gets the prompt for choices. 85 * 86 * @return string 87 */ 88 public function getPrompt() 89 { 90 return $this->prompt; 91 } 92 93 /** 94 * Sets the prompt for choices. 95 * 96 * @param string $prompt 97 * 98 * @return $this 99 */ 100 public function setPrompt($prompt) 101 { 102 $this->prompt = $prompt; 103 104 return $this; 105 } 106 107 /** 108 * Sets the error message for invalid values. 109 * 110 * The error message has a string placeholder (%s) for the invalid value. 111 * 112 * @param string $errorMessage 113 * 114 * @return $this 115 */ 116 public function setErrorMessage($errorMessage) 117 { 118 $this->errorMessage = $errorMessage; 119 $this->setValidator($this->getDefaultValidator()); 120 121 return $this; 122 } 123 124 private function getDefaultValidator(): callable 125 { 126 $choices = $this->choices; 127 $errorMessage = $this->errorMessage; 128 $multiselect = $this->multiselect; 129 $isAssoc = $this->isAssoc($choices); 130 131 return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { 132 if ($multiselect) { 133 // Check for a separated comma values 134 if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) { 135 throw new InvalidArgumentException(sprintf($errorMessage, $selected)); 136 } 137 138 $selectedChoices = explode(',', $selected); 139 } else { 140 $selectedChoices = [$selected]; 141 } 142 143 if ($this->isTrimmable()) { 144 foreach ($selectedChoices as $k => $v) { 145 $selectedChoices[$k] = trim($v); 146 } 147 } 148 149 $multiselectChoices = []; 150 foreach ($selectedChoices as $value) { 151 $results = []; 152 foreach ($choices as $key => $choice) { 153 if ($choice === $value) { 154 $results[] = $key; 155 } 156 } 157 158 if (\count($results) > 1) { 159 throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); 160 } 161 162 $result = array_search($value, $choices); 163 164 if (!$isAssoc) { 165 if (false !== $result) { 166 $result = $choices[$result]; 167 } elseif (isset($choices[$value])) { 168 $result = $choices[$value]; 169 } 170 } elseif (false === $result && isset($choices[$value])) { 171 $result = $value; 172 } 173 174 if (false === $result) { 175 throw new InvalidArgumentException(sprintf($errorMessage, $value)); 176 } 177 178 $multiselectChoices[] = (string) $result; 179 } 180 181 if ($multiselect) { 182 return $multiselectChoices; 183 } 184 185 return current($multiselectChoices); 186 }; 187 } 188} 189