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; 15use Symfony\Component\Console\Exception\LogicException; 16 17/** 18 * Represents a Question. 19 * 20 * @author Fabien Potencier <fabien@symfony.com> 21 */ 22class Question 23{ 24 private $question; 25 private $attempts; 26 private $hidden = false; 27 private $hiddenFallback = true; 28 private $autocompleterCallback; 29 private $validator; 30 private $default; 31 private $normalizer; 32 private $trimmable = true; 33 34 /** 35 * @param string $question The question to ask to the user 36 * @param mixed $default The default answer to return if the user enters nothing 37 */ 38 public function __construct(string $question, $default = null) 39 { 40 $this->question = $question; 41 $this->default = $default; 42 } 43 44 /** 45 * Returns the question. 46 * 47 * @return string 48 */ 49 public function getQuestion() 50 { 51 return $this->question; 52 } 53 54 /** 55 * Returns the default answer. 56 * 57 * @return mixed 58 */ 59 public function getDefault() 60 { 61 return $this->default; 62 } 63 64 /** 65 * Returns whether the user response must be hidden. 66 * 67 * @return bool 68 */ 69 public function isHidden() 70 { 71 return $this->hidden; 72 } 73 74 /** 75 * Sets whether the user response must be hidden or not. 76 * 77 * @param bool $hidden 78 * 79 * @return $this 80 * 81 * @throws LogicException In case the autocompleter is also used 82 */ 83 public function setHidden($hidden) 84 { 85 if ($this->autocompleterCallback) { 86 throw new LogicException('A hidden question cannot use the autocompleter.'); 87 } 88 89 $this->hidden = (bool) $hidden; 90 91 return $this; 92 } 93 94 /** 95 * In case the response can not be hidden, whether to fallback on non-hidden question or not. 96 * 97 * @return bool 98 */ 99 public function isHiddenFallback() 100 { 101 return $this->hiddenFallback; 102 } 103 104 /** 105 * Sets whether to fallback on non-hidden question if the response can not be hidden. 106 * 107 * @param bool $fallback 108 * 109 * @return $this 110 */ 111 public function setHiddenFallback($fallback) 112 { 113 $this->hiddenFallback = (bool) $fallback; 114 115 return $this; 116 } 117 118 /** 119 * Gets values for the autocompleter. 120 * 121 * @return iterable|null 122 */ 123 public function getAutocompleterValues() 124 { 125 $callback = $this->getAutocompleterCallback(); 126 127 return $callback ? $callback('') : null; 128 } 129 130 /** 131 * Sets values for the autocompleter. 132 * 133 * @param iterable|null $values 134 * 135 * @return $this 136 * 137 * @throws InvalidArgumentException 138 * @throws LogicException 139 */ 140 public function setAutocompleterValues($values) 141 { 142 if (\is_array($values)) { 143 $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); 144 145 $callback = static function () use ($values) { 146 return $values; 147 }; 148 } elseif ($values instanceof \Traversable) { 149 $valueCache = null; 150 $callback = static function () use ($values, &$valueCache) { 151 return $valueCache ?? $valueCache = iterator_to_array($values, false); 152 }; 153 } elseif (null === $values) { 154 $callback = null; 155 } else { 156 throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.'); 157 } 158 159 return $this->setAutocompleterCallback($callback); 160 } 161 162 /** 163 * Gets the callback function used for the autocompleter. 164 */ 165 public function getAutocompleterCallback(): ?callable 166 { 167 return $this->autocompleterCallback; 168 } 169 170 /** 171 * Sets the callback function used for the autocompleter. 172 * 173 * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. 174 * 175 * @return $this 176 */ 177 public function setAutocompleterCallback(callable $callback = null): self 178 { 179 if ($this->hidden && null !== $callback) { 180 throw new LogicException('A hidden question cannot use the autocompleter.'); 181 } 182 183 $this->autocompleterCallback = $callback; 184 185 return $this; 186 } 187 188 /** 189 * Sets a validator for the question. 190 * 191 * @return $this 192 */ 193 public function setValidator(callable $validator = null) 194 { 195 $this->validator = $validator; 196 197 return $this; 198 } 199 200 /** 201 * Gets the validator for the question. 202 * 203 * @return callable|null 204 */ 205 public function getValidator() 206 { 207 return $this->validator; 208 } 209 210 /** 211 * Sets the maximum number of attempts. 212 * 213 * Null means an unlimited number of attempts. 214 * 215 * @param int|null $attempts 216 * 217 * @return $this 218 * 219 * @throws InvalidArgumentException in case the number of attempts is invalid 220 */ 221 public function setMaxAttempts($attempts) 222 { 223 if (null !== $attempts && $attempts < 1) { 224 throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); 225 } 226 227 $this->attempts = $attempts; 228 229 return $this; 230 } 231 232 /** 233 * Gets the maximum number of attempts. 234 * 235 * Null means an unlimited number of attempts. 236 * 237 * @return int|null 238 */ 239 public function getMaxAttempts() 240 { 241 return $this->attempts; 242 } 243 244 /** 245 * Sets a normalizer for the response. 246 * 247 * The normalizer can be a callable (a string), a closure or a class implementing __invoke. 248 * 249 * @return $this 250 */ 251 public function setNormalizer(callable $normalizer) 252 { 253 $this->normalizer = $normalizer; 254 255 return $this; 256 } 257 258 /** 259 * Gets the normalizer for the response. 260 * 261 * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. 262 * 263 * @return callable|null 264 */ 265 public function getNormalizer() 266 { 267 return $this->normalizer; 268 } 269 270 protected function isAssoc($array) 271 { 272 return (bool) \count(array_filter(array_keys($array), 'is_string')); 273 } 274 275 public function isTrimmable(): bool 276 { 277 return $this->trimmable; 278 } 279 280 /** 281 * @return $this 282 */ 283 public function setTrimmable(bool $trimmable): self 284 { 285 $this->trimmable = $trimmable; 286 287 return $this; 288 } 289} 290