21
21
class DialogHelper extends Helper
22
22
{
23
23
private $ inputStream ;
24
+ private static $ shell ;
25
+ private static $ stty ;
24
26
25
27
/**
26
28
* Asks a question to the user.
@@ -71,6 +73,72 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
71
73
return !$ answer || 'y ' == strtolower ($ answer [0 ]);
72
74
}
73
75
76
+ /**
77
+ * Ask a question to the user, the response is hidden
78
+ *
79
+ * @param OutputInterface $output An Output instance
80
+ * @param string|array $question The question
81
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
82
+ *
83
+ * @return string The answer
84
+ *
85
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
86
+ */
87
+ public function askHiddenResponse (OutputInterface $ output , $ question , $ fallback = true )
88
+ {
89
+ if (defined ('PHP_WINDOWS_VERSION_BUILD ' )) {
90
+ $ exe = __DIR__ . '\\hiddeninput.exe ' ;
91
+
92
+ // handle code running from a phar
93
+ if ('phar: ' === substr (__FILE__ , 0 , 5 )) {
94
+ $ tmpExe = sys_get_temp_dir () . '/hiddeninput.exe ' ;
95
+ copy ($ exe , $ tmpExe );
96
+ $ exe = $ tmpExe ;
97
+ }
98
+
99
+ $ output ->write ($ question );
100
+ $ value = rtrim (shell_exec ($ exe ));
101
+ $ output ->writeln ('' );
102
+
103
+ if (isset ($ tmpExe )) {
104
+ unlink ($ tmpExe );
105
+ }
106
+
107
+ return $ value ;
108
+ } elseif ($ this ->hasSttyAvailable ()) {
109
+
110
+ $ output ->write ($ question );
111
+
112
+ $ sttyMode = shell_exec ('/usr/bin/env stty -g ' );
113
+
114
+ shell_exec ('/usr/bin/env stty -echo ' );
115
+ $ value = fgets ($ this ->inputStream ?: STDIN , 4096 );
116
+ shell_exec (sprintf ('/usr/bin/env stty %s ' , escapeshellarg ($ sttyMode )));
117
+
118
+ if (false === $ value ) {
119
+ throw new \RuntimeException ('Aborted ' );
120
+ }
121
+
122
+ $ value = trim ($ value );
123
+ $ output ->writeln ('' );
124
+
125
+ return $ value ;
126
+ } elseif (false !== $ shell = $ this ->getShell ()) {
127
+
128
+ $ output ->write ($ question );
129
+ $ readCmd = $ shell === 'csh ' ? 'set mypassword = $< ' : 'read mypassword ' ;
130
+ $ command = sprintf ("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword' " , $ shell , $ readCmd );
131
+ $ value = rtrim (shell_exec ($ command ));
132
+ $ output ->writeln ('' );
133
+
134
+ return $ value ;
135
+ } elseif ($ fallback ) {
136
+ return $ this ->ask ($ output , $ question );
137
+ }
138
+
139
+ throw new \RuntimeException ('Unable to hide the response ' );
140
+ }
141
+
74
142
/**
75
143
* Asks for a value and validates the response.
76
144
*
@@ -80,7 +148,7 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
80
148
*
81
149
* @param OutputInterface $output An Output instance
82
150
* @param string|array $question The question to ask
83
- * @param callback $validator A PHP callback
151
+ * @param callable $validator A PHP callback
84
152
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
85
153
* @param string $default The default answer if none is given by the user
86
154
*
@@ -90,21 +158,39 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
90
158
*/
91
159
public function askAndValidate (OutputInterface $ output , $ question , $ validator , $ attempts = false , $ default = null )
92
160
{
93
- $ error = null ;
94
- while (false === $ attempts || $ attempts --) {
95
- if (null !== $ error ) {
96
- $ output ->writeln ($ this ->getHelperSet ()->get ('formatter ' )->formatBlock ($ error ->getMessage (), 'error ' ));
97
- }
161
+ $ interviewer = function () use ($ output , $ question , $ default ) {
162
+ return $ this ->ask ($ output , $ question , $ default );
163
+ };
98
164
99
- $ value = $ this ->ask ($ output , $ question , $ default );
165
+ return $ this ->validateAttempts ($ interviewer , $ output , $ validator , $ attempts );
166
+ }
100
167
101
- try {
102
- return call_user_func ($ validator , $ value );
103
- } catch (\Exception $ error ) {
104
- }
105
- }
168
+ /**
169
+ * Asks for a value, hide and validates the response.
170
+ *
171
+ * The validator receives the data to validate. It must return the
172
+ * validated data when the data is valid and throw an exception
173
+ * otherwise.
174
+ *
175
+ * @param OutputInterface $output An Output instance
176
+ * @param string|array $question The question to ask
177
+ * @param callable $validator A PHP callback
178
+ * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
179
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
180
+ *
181
+ * @return string The response
182
+ *
183
+ * @throws \Exception When any of the validators return an error
184
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
185
+ *
186
+ */
187
+ public function askHiddenResponseAndValidate (OutputInterface $ output , $ question , $ validator , $ attempts = false , $ fallback = true )
188
+ {
189
+ $ interviewer = function () use ($ output , $ question , $ fallback ) {
190
+ return $ this ->askHiddenResponse ($ output , $ question , $ fallback );
191
+ };
106
192
107
- throw $ error ;
193
+ return $ this -> validateAttempts ( $ interviewer , $ output , $ validator , $ attempts ) ;
108
194
}
109
195
110
196
/**
@@ -136,4 +222,71 @@ public function getName()
136
222
{
137
223
return 'dialog ' ;
138
224
}
225
+
226
+ /**
227
+ * Return a valid unix shell
228
+ *
229
+ * @return string|false The valid shell name, false in case no valid shell is found
230
+ */
231
+ private function getShell ()
232
+ {
233
+ if (null !== self ::$ shell ) {
234
+ return self ::$ shell ;
235
+ }
236
+
237
+ self ::$ shell = false ;
238
+
239
+ if (file_exists ('/usr/bin/env ' )) {
240
+ // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
241
+ $ test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null " ;
242
+ foreach (array ('bash ' , 'zsh ' , 'ksh ' , 'csh ' ) as $ sh ) {
243
+ if ('OK ' === rtrim (shell_exec (sprintf ($ test , $ sh )))) {
244
+ self ::$ shell = $ sh ;
245
+ break ;
246
+ }
247
+ }
248
+ }
249
+
250
+ return self ::$ shell ;
251
+ }
252
+
253
+ private function hasSttyAvailable ()
254
+ {
255
+ if (null !== self ::$ stty ) {
256
+ return self ::$ stty ;
257
+ }
258
+
259
+ exec ('/usr/bin/env stty ' , $ output , $ exicode );
260
+
261
+ return self ::$ stty = $ exicode === 0 ;
262
+ }
263
+
264
+ /**
265
+ * Validate an attempt
266
+ *
267
+ * @param callable $interviewer A callable that will ask for a question and return the result
268
+ * @param OutputInterface $output An Output instance
269
+ * @param callable $validator A PHP callback
270
+ * @param integer $attempts Max number of times to ask before giving up ; false will ask infinitely
271
+ *
272
+ * @return string The validated response
273
+ *
274
+ * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
275
+ */
276
+ private function validateAttempts ($ interviewer , OutputInterface $ output , $ validator , $ attempts )
277
+ {
278
+ $ error = null ;
279
+ while (false === $ attempts || $ attempts --) {
280
+ if (null !== $ error ) {
281
+ $ output ->writeln ($ this ->getHelperSet ()->get ('formatter ' )->formatBlock ($ error ->getMessage (), 'error ' ));
282
+ }
283
+
284
+ try {
285
+ return call_user_func ($ validator , $ interviewer ());
286
+ } catch (\Exception $ error ) {
287
+ }
288
+ }
289
+
290
+ throw $ error ;
291
+ }
139
292
}
0 commit comments