Skip to content

Commit f4ee337

Browse files
committed
[Console] Add documentation for QuestionHelper
1 parent d2faada commit f4ee337

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed

components/console/helpers/dialoghelper.rst

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
Dialog Helper
55
=============
66

7+
.. caution::
8+
9+
The Dialog Helper was deprecated in Symfony 2.5 and will be removed in
10+
Symfony 3.0. You should now use the
11+
:doc:`Question Helper </components/console/helpers/questionhelper>` instead,
12+
which is simpler to use.
13+
714
The :class:`Symfony\\Component\\Console\\Helper\\DialogHelper` provides
815
functions to ask the user for more information. It is included in the default
916
helper set, which you can get by calling
@@ -149,6 +156,8 @@ You can also ask and validate a hidden response::
149156
if (trim($value) == '') {
150157
throw new \Exception('The password can not be empty');
151158
}
159+
160+
return $value;
152161
};
153162

154163
$password = $dialog->askHiddenResponseAndValidate(

components/console/helpers/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The Console Helpers
1111
formatterhelper
1212
progressbar
1313
progresshelper
14+
questionhelper
1415
table
1516
tablehelper
1617

components/console/helpers/map.rst.inc

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
* :doc:`/components/console/helpers/formatterhelper`
33
* :doc:`/components/console/helpers/progressbar`
44
* :doc:`/components/console/helpers/progresshelper`
5+
* :doc:`/components/console/helpers/questionhelper`
56
* :doc:`/components/console/helpers/table`
67
* :doc:`/components/console/helpers/tablehelper`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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

Comments
 (0)