Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e396edb

Browse filesBrowse files
committed
[Console] Add DialogHelper::askHiddenResponse method
1 parent ccba363 commit e396edb
Copy full SHA for e396edb

File tree

Expand file treeCollapse file tree

3 files changed

+175
-13
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+175
-13
lines changed

‎src/Symfony/Component/Console/Helper/DialogHelper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Helper/DialogHelper.php
+166-13Lines changed: 166 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
class DialogHelper extends Helper
2222
{
2323
private $inputStream;
24+
private static $shell;
25+
private static $stty;
2426

2527
/**
2628
* Asks a question to the user.
@@ -71,6 +73,72 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
7173
return !$answer || 'y' == strtolower($answer[0]);
7274
}
7375

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+
74142
/**
75143
* Asks for a value and validates the response.
76144
*
@@ -80,7 +148,7 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
80148
*
81149
* @param OutputInterface $output An Output instance
82150
* @param string|array $question The question to ask
83-
* @param callback $validator A PHP callback
151+
* @param callable $validator A PHP callback
84152
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
85153
* @param string $default The default answer if none is given by the user
86154
*
@@ -90,21 +158,39 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
90158
*/
91159
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null)
92160
{
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+
};
98164

99-
$value = $this->ask($output, $question, $default);
165+
return $this->validateAttempts($interviewer, $output, $validator, $attempts);
166+
}
100167

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+
};
106192

107-
throw $error;
193+
return $this->validateAttempts($interviewer, $output, $validator, $attempts);
108194
}
109195

110196
/**
@@ -136,4 +222,71 @@ public function getName()
136222
{
137223
return 'dialog';
138224
}
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+
}
139292
}
Binary file not shown.

‎src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ public function testAsk()
3131
$this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
3232
}
3333

34+
public function testAskHiddenResponse()
35+
{
36+
$dialog = new DialogHelper();
37+
38+
$dialog->setInputStream($this->getInputStream("8AM\n"));
39+
40+
$this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?'));
41+
}
42+
3443
public function testAskConfirmation()
3544
{
3645
$dialog = new DialogHelper();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.