|
| 1 | +.. index:: |
| 2 | + single: Console Helpers; Question Helper |
| 3 | + |
| 4 | +Question Helper |
| 5 | +=============== |
| 6 | + |
| 7 | +.. versionadded:: 2.5 |
| 8 | + The Question Helper was introduced in Symfony 2.5. |
| 9 | + |
| 10 | +The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides |
| 11 | +functions to ask the user for more information. It is included in the default |
| 12 | +helper set, which you can get by calling |
| 13 | +:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: |
| 14 | + |
| 15 | + $helper = $this->getHelperSet()->get('question'); |
| 16 | + |
| 17 | +The Question Helper has a single method |
| 18 | +:method:`Symfony\\Component\\Console\\Command\\Command::ask` that needs an |
| 19 | +:class:`Symfony\\Component\\Console\\Output\\InputInterface` instance as the |
| 20 | +first argument, an :class:`Symfony\\Component\\Console\\Output\\OutputInterface` |
| 21 | +instance as the second argument and a |
| 22 | +:class:`Symfony\\Component\\Console\\Question\\Question` as last argument. |
| 23 | + |
| 24 | +Asking the User for Confirmation |
| 25 | +-------------------------------- |
| 26 | + |
| 27 | +Suppose you want to confirm an action before actually executing it. Add |
| 28 | +the following to your command:: |
| 29 | + |
| 30 | + use Symfony\Component\Console\Question\ConfirmationQuestion; |
| 31 | + // ... |
| 32 | + |
| 33 | + $helper = $this->getHelperSet()->get('question'); |
| 34 | + $question = new ConfirmationQuestion('Continue with this action?', false); |
| 35 | + |
| 36 | + if (!$helper->ask($input, $output, $question)) { |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | +In this case, the user will be asked "Continue with this action?". If the user |
| 41 | +answers with ``y`` it returns ``true`` or ``false`` if they answer with ``n``. |
| 42 | +The second argument to |
| 43 | +:method:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion::__construct` |
| 44 | +is the default value to return if the user doesn't enter any input. Any other |
| 45 | +input will ask the same question again. |
| 46 | + |
| 47 | +Asking the User for Information |
| 48 | +------------------------------- |
| 49 | + |
| 50 | +You can also ask a question with more than a simple yes/no answer. For instance, |
| 51 | +if you want to know a bundle name, you can add this to your command:: |
| 52 | + |
| 53 | + use Symfony\Component\Console\Question\Question; |
| 54 | + // ... |
| 55 | + |
| 56 | + $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); |
| 57 | + |
| 58 | + $bundle = $helper->ask($input, $output, $question); |
| 59 | + |
| 60 | +The user will be asked "Please enter the name of the bundle". They can type |
| 61 | +some name which will be returned by the |
| 62 | +:method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` method. |
| 63 | +If they leave it empty, the default value (``AcmeDemoBundle`` here) is returned. |
| 64 | + |
| 65 | +Let the User Choose from a List of Answers |
| 66 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 67 | + |
| 68 | +If you have a predefined set of answers the user can choose from, you |
| 69 | +could use a :class:`Symfony\\Component\\Console\\Question\\ChoiceQuestion` |
| 70 | +which makes sure that the user can only enter a valid string |
| 71 | +from a predefined list:: |
| 72 | + |
| 73 | + use Symfony\Component\Console\Question\ChoiceQuestion; |
| 74 | + // ... |
| 75 | + |
| 76 | + $helper = $app->getHelperSet()->get('question'); |
| 77 | + $question = new ChoiceQuestion( |
| 78 | + 'Please select your favorite color (defaults to red)', |
| 79 | + array('red', 'blue', 'yellow'), |
| 80 | + 'red' |
| 81 | + ); |
| 82 | + $question->setErrorMessage('Color %s is invalid.'); |
| 83 | + |
| 84 | + $color = $helper->ask($input, $output, $question); |
| 85 | + $output->writeln('You have just selected: '.$color); |
| 86 | + |
| 87 | + // ... do something with the color |
| 88 | + |
| 89 | +The option which should be selected by default is provided with the third |
| 90 | +argument of the constructor. The default is ``null``, which means that no |
| 91 | +option is the default one. |
| 92 | + |
| 93 | +If the user enters an invalid string, an error message is shown and the user |
| 94 | +is asked to provide the answer another time, until they enter a valid string |
| 95 | +or reach the maximum number of attempts. The default value for the maximum number |
| 96 | +of attempts is ``null``, which means infinite number attempts. You can define |
| 97 | +your own error message using |
| 98 | +:method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setErrorMessage`. |
| 99 | + |
| 100 | +Multiple Choices |
| 101 | +................ |
| 102 | + |
| 103 | +Sometimes, multiple answers can be given. The ``ChoiceQuestion`` provides this |
| 104 | +feature using comma separated values. This is disabled by default, to enable |
| 105 | +this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMultiselect`:: |
| 106 | + |
| 107 | + use Symfony\Component\Console\Question\ChoiceQuestion; |
| 108 | + // ... |
| 109 | + |
| 110 | + $helper = $app->getHelperSet()->get('question'); |
| 111 | + $question = new ChoiceQuestion( |
| 112 | + 'Please select your favorite color (defaults to red)', |
| 113 | + array('red', 'blue', 'yellow'), |
| 114 | + 'red' |
| 115 | + ); |
| 116 | + $question->setMultiselect(true); |
| 117 | + |
| 118 | + $colors = $helper->ask($input, $output, $question); |
| 119 | + $output->writeln('You have just selected: ' . implode(', ', $colors)); |
| 120 | + |
| 121 | +Now, when the user enters ``1,2``, the result will be: |
| 122 | +``You have just selected: blue, yellow``. |
| 123 | + |
| 124 | +Autocompletion |
| 125 | +~~~~~~~~~~~~~~ |
| 126 | + |
| 127 | +You can also specify an array of potential answers for a given question. These |
| 128 | +will be autocompleted as the user types:: |
| 129 | + |
| 130 | + use Symfony\Component\Console\Question\Question; |
| 131 | + // ... |
| 132 | + |
| 133 | + $bundles = array('AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle'); |
| 134 | + $question = new Question('Please enter the name of a bundle', 'FooBundle'); |
| 135 | + $question->setAutocompleterValues($bundles); |
| 136 | + |
| 137 | + $name = $helper->ask($input, $output, $question); |
| 138 | + |
| 139 | +Hiding the User's Response |
| 140 | +~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 141 | + |
| 142 | +You can also ask a question and hide the response. This is particularly |
| 143 | +convenient for passwords:: |
| 144 | + |
| 145 | + use Symfony\Component\Console\Question\Question; |
| 146 | + // ... |
| 147 | + |
| 148 | + $question = new Question('What is the database password?'); |
| 149 | + $question->setHidden(true); |
| 150 | + $question->setHiddenFallback(false); |
| 151 | + |
| 152 | + $password = $helper->ask($input, $output, $question); |
| 153 | + |
| 154 | +.. caution:: |
| 155 | + |
| 156 | + When you ask for a hidden response, Symfony will use either a binary, change |
| 157 | + stty mode or use another trick to hide the response. If none is available, |
| 158 | + it will fallback and allow the response to be visible unless you set this |
| 159 | + behavior to ``false`` using |
| 160 | + :method:`Symfony\\Component\\Console\\Question\\Question::setHiddenFallback` |
| 161 | + like in the example above. In this case, a ``RuntimeException`` |
| 162 | + would be thrown. |
| 163 | + |
| 164 | +Validating the Answer |
| 165 | +--------------------- |
| 166 | + |
| 167 | +You can even validate the answer. For instance, in a previous example you asked |
| 168 | +for the bundle name. Following the Symfony naming conventions, it should |
| 169 | +be suffixed with ``Bundle``. You can validate that by using the |
| 170 | +:method:`Symfony\\Component\\Console\\Question\\Question::setValidator` |
| 171 | +method:: |
| 172 | + |
| 173 | + use Symfony\Component\Console\Question\Question; |
| 174 | + // ... |
| 175 | + |
| 176 | + $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); |
| 177 | + $question->setValidator(function ($answer) { |
| 178 | + if ('Bundle' !== substr($answer, -6)) { |
| 179 | + throw new \RuntimeException( |
| 180 | + 'The name of the bundle should be suffixed with \'Bundle\'' |
| 181 | + ); |
| 182 | + } |
| 183 | + return $answer; |
| 184 | + }); |
| 185 | + $question->setMaxAttempts(2); |
| 186 | + |
| 187 | + $name = $helper->ask($input, $output, $question); |
| 188 | + |
| 189 | +The ``$validator`` is a callback which handles the validation. It should |
| 190 | +throw an exception if there is something wrong. The exception message is displayed |
| 191 | +in the console, so it is a good practice to put some useful information in it. The |
| 192 | +callback function should also return the value of the user's input if the validation |
| 193 | +was successful. |
| 194 | + |
| 195 | +You can set the max number of times to ask with the |
| 196 | +:method:`Symfony\\Component\\Console\\Question\\Question::setMaxAttempts` method. |
| 197 | +If you reach this max number it will use the default value. Using ``null`` means |
| 198 | +the amount of attempts is infinite. The user will be asked as long as they provide an |
| 199 | +invalid answer and will only be able to proceed if their input is valid. |
| 200 | + |
| 201 | +Validating a Hidden Response |
| 202 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 203 | + |
| 204 | +You can also use a validator with a hidden question:: |
| 205 | + |
| 206 | + use Symfony\Component\Console\Question\Question; |
| 207 | + // ... |
| 208 | + |
| 209 | + $helper = $this->getHelperSet()->get('question'); |
| 210 | + |
| 211 | + $question = new Question('Please enter your password'); |
| 212 | + $question->setValidator(function ($value) { |
| 213 | + if (trim($value) == '') { |
| 214 | + throw new \Exception('The password can not be empty'); |
| 215 | + } |
| 216 | + |
| 217 | + return $value; |
| 218 | + }); |
| 219 | + $question->setHidden(true); |
| 220 | + $question->setMaxAttempts(20); |
| 221 | + |
| 222 | + $password = $helper->ask($input, $output, $question); |
| 223 | + |
| 224 | + |
| 225 | +Testing a Command that Expects Input |
| 226 | +------------------------------------ |
| 227 | + |
| 228 | +If you want to write a unit test for a command which expects some kind of input |
| 229 | +from the command line, you need to set the helper input stream:: |
| 230 | + |
| 231 | + use Symfony\Component\Console\Helper\QuestionHelper; |
| 232 | + use Symfony\Component\Console\Helper\HelperSet; |
| 233 | + use Symfony\Component\Console\Tester\CommandTester; |
| 234 | + |
| 235 | + // ... |
| 236 | + public function testExecute() |
| 237 | + { |
| 238 | + // ... |
| 239 | + $commandTester = new CommandTester($command); |
| 240 | + |
| 241 | + $helper = $command->getHelper('question'); |
| 242 | + $helper->setInputStream($this->getInputStream('Test\\n')); |
| 243 | + // Equals to a user inputting "Test" and hitting ENTER |
| 244 | + // If you need to enter a confirmation, "yes\n" will work |
| 245 | + |
| 246 | + $commandTester->execute(array('command' => $command->getName())); |
| 247 | + |
| 248 | + // $this->assertRegExp('/.../', $commandTester->getDisplay()); |
| 249 | + } |
| 250 | + |
| 251 | + protected function getInputStream($input) |
| 252 | + { |
| 253 | + $stream = fopen('php://memory', 'r+', false); |
| 254 | + fputs($stream, $input); |
| 255 | + rewind($stream); |
| 256 | + |
| 257 | + return $stream; |
| 258 | + } |
| 259 | + |
| 260 | +By setting the input stream of the ``QuestionHelper``, you imitate what the |
| 261 | +console would do internally with all user input through the cli. This way |
| 262 | +you can test any user interaction (even complex ones) by passing an appropriate |
| 263 | +input stream. |
0 commit comments