Question.php 7.5 KB

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