Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[Console] Add DialogHelper::askHiddenResponse method #5731

Merged
merged 6 commits into from

5 participants

@romainneutron
Collaborator

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

It adds a method to DialogHelper to ask a question and hide the response. It's pretty cool when working with passwords.

This code is more than largely inspired by Composer, see ConsoleIO.php at line 140

You will notice that this PR embeds a Windows Executable binary for windows support. This windows binary is provided by @Seldaek (see https://github.com/Seldaek/hidden-input)
This dependency is not yet available via composer.

If this is a problem to embed this file, we can think of other way to provide this support (make a package from HiddenInput and add composer recommandation for example).

src/Symfony/Component/Console/Helper/DialogHelper.php
((20 lines not shown))
+ if ('phar:' === substr(__FILE__, 0, 5)) {
+ $tmpExe = sys_get_temp_dir() . '/hiddeninput.exe';
+ copy($exe, $tmpExe);
+ $exe = $tmpExe;
+ }
+
+ $output->write($question);
+ $value = rtrim(shell_exec($exe));
+ $output->writeln('');
+
+ if (isset($tmpExe)) {
+ unlink($tmpExe);
+ }
+
+ return $value;
+ } elseif ($this->hasSttyAvailable()) {

you don't need the elseif, just if

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/Console/Helper/DialogHelper.php
((38 lines not shown))
+
+ $sttyMode = shell_exec('/usr/bin/env stty -g');
+
+ shell_exec('/usr/bin/env stty -echo');
+ $value = fgets($this->inputStream ?: STDIN, 4096);
+ shell_exec(sprintf('/usr/bin/env stty %s', escapeshellarg($sttyMode)));
+
+ if (false === $value) {
+ throw new \RuntimeException('Aborted');
+ }
+
+ $value = trim($value);
+ $output->writeln('');
+
+ return $value;
+ } elseif (false !== $shell = $this->getShell()) {

you don't need the elseif, just if

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/Console/Helper/DialogHelper.php
((47 lines not shown))
+ }
+
+ $value = trim($value);
+ $output->writeln('');
+
+ return $value;
+ } elseif (false !== $shell = $this->getShell()) {
+
+ $output->write($question);
+ $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read mypassword';
+ $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
+ $value = rtrim(shell_exec($command));
+ $output->writeln('');
+
+ return $value;
+ } elseif ($fallback) {

same..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -72,6 +74,78 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
}
/**
+ * Ask a question to the user, the response is hidden
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The answer
+ *
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
+ */
+ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $exe = __DIR__ . '\\hiddeninput.exe';
@stof Collaborator
stof added a note

use a / in the path, to be consistent with all other places in Symfony. As of PHP 5.3, / works in paths on windows too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof
Collaborator

The link to the hiddeninput source code should be added in the readme.
And you should also update the changelog.

Btw, adding composer for hiddeninput does not make sense. Compsoer is about installing PHP code, not about downloading the source of a C++ program.

@romainneutron
Collaborator
@romainneutron
Collaborator

Changelog updated, Readme note added, CS fixed

@stof stof commented on the diff
...y/Component/Console/Tests/Helper/DialogHelperTest.php
@@ -31,6 +31,15 @@ public function testAsk()
$this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
}
+ public function testAskHiddenResponse()
+ {
+ $dialog = new DialogHelper();
+
+ $dialog->setInputStream($this->getInputStream("8AM\n"));
+
+ $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?'));
+ }
@stof Collaborator
stof added a note

This test should be skipped on Windows as it would trigger the binary, which will not work.

@romainneutron Collaborator

It's fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof
Collaborator

the missing point is now the PR to the doc for this new feature

src/Symfony/Component/Console/README.md
((6 lines not shown))
You can run the unit tests with the following command:
phpunit
+Third Party
+-----------
+
+`HiddenInput.exe` third party binary is provided within this component. Find
@Seldaek Collaborator
Seldaek added a note

The file is called hiddeninput.exe all lowercased

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@romainneutron
Collaborator

@stof documentation added

src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -72,6 +74,78 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
}
/**
+ * Ask a question to the user, the response is hidden
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The answer
+ *
+ * @throws \RuntimeException In case the fallback is disactivated and the response can not be hidden
@fabpot Owner
fabpot added a note

disactivated should be deactivated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -72,6 +74,78 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
}
/**
+ * Ask a question to the user, the response is hidden
@fabpot Owner
fabpot added a note

Ask should be Asks and there is a missing dot at the end of the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fabpot fabpot commented on the diff
src/Symfony/Component/Console/Helper/DialogHelper.php
((23 lines not shown))
+ $exe = $tmpExe;
+ }
+
+ $output->write($question);
+ $value = rtrim(shell_exec($exe));
+ $output->writeln('');
+
+ if (isset($tmpExe)) {
+ unlink($tmpExe);
+ }
+
+ return $value;
+ }
+
+ if ($this->hasSttyAvailable()) {
+
@fabpot Owner
fabpot added a note

this blank line should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fabpot fabpot commented on the diff
src/Symfony/Component/Console/Helper/DialogHelper.php
((43 lines not shown))
+ shell_exec('/usr/bin/env stty -echo');
+ $value = fgets($this->inputStream ?: STDIN, 4096);
+ shell_exec(sprintf('/usr/bin/env stty %s', $sttyMode));
+
+ if (false === $value) {
+ throw new \RuntimeException('Aborted');
+ }
+
+ $value = trim($value);
+ $output->writeln('');
+
+ return $value;
+ }
+
+ if (false !== $shell = $this->getShell()) {
+
@fabpot Owner
fabpot added a note

this blank line should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/Console/README.md
((6 lines not shown))
You can run the unit tests with the following command:
phpunit
+Third Party
+-----------
+
+`hiddeninput.exe` third party binary is provided within this component. Find
@fabpot Owner
fabpot added a note

I would have stored the executable under a Resources/bin directory like done in some other components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@romainneutron
Collaborator

@fabpot what you asked is now fixed

@fabpot fabpot referenced this pull request from a commit
@fabpot fabpot merged branch romainneutron/ConsoleHiddenQuestion (PR #5731)
This PR was merged into the master branch.

Commits
-------

aefa495 Move `hiddeninput.exe` to Resources/bin
c0f8a63 Fix CS and typos
26c35e0 Skip askHiddenResponse test on windows
e2eaf5a Update Changelog, add Readme note about hidden input third party
ac01d5d Fix tests and CS
e396edb [Console] Add DialogHelper::askHiddenResponse method

Discussion
----------

[Console] Add DialogHelper::askHiddenResponse method

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

It adds a method to `DialogHelper` to ask a question and hide the response. It's pretty cool when working with passwords.

This code is more than largely inspired by Composer, see [ConsoleIO.php at line 140](https://github.com/composer/composer/blob/master/src/Composer/IO/ConsoleIO.php#L140)

 You will notice that this PR embeds a Windows Executable binary for windows support. This windows binary is provided by @Seldaek (see https://github.com/Seldaek/hidden-input)
This dependency is not yet available via composer.

If this is a problem to embed this file, we can think of other way to provide this support (make a package from HiddenInput and add composer recommandation for example).

---------------------------------------------------------------------------

by stof at 2012-10-11T17:20:11Z

The link to the hiddeninput source code should be added in the readme.
And you should also update the changelog.

Btw, adding composer for hiddeninput does not make sense. Compsoer is about installing PHP code, not about downloading the source of a C++ program.

---------------------------------------------------------------------------

by romainneutron at 2012-10-11T17:22:58Z

This proposition comes from a discussion I had with Jordi , nothing more :)

Romain

On 11 oct. 2012, at 19:20, Christophe Coevoet <notifications@github.com>
wrote:

The link to the hiddeninput source code should be added in the readme.
And you should also update the changelog.

Btw, adding composer for hiddeninput does not make sense. Compsoer is about
installing PHP code, not about downloading the source of a C++ program.

—
Reply to this email directly or view it on
GitHub<#5731 (comment)>.

---------------------------------------------------------------------------

by romainneutron at 2012-10-12T07:33:00Z

Changelog updated, Readme note added, CS fixed

---------------------------------------------------------------------------

by stof at 2012-10-13T22:09:24Z

the missing point is now the PR to the doc for this new feature

---------------------------------------------------------------------------

by romainneutron at 2012-10-16T00:33:59Z

@stof documentation added

---------------------------------------------------------------------------

by romainneutron at 2012-10-16T09:10:35Z

@fabpot what you asked is now fixed
d8f6021
@fabpot fabpot merged commit aefa495 into from
@vicb vicb referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@mmucklo mmucklo referenced this pull request from a commit
@fabpot fabpot merged branch romainneutron/ConsoleHiddenQuestion (PR #5731)
This PR was merged into the master branch.

Commits
-------

aefa495 Move `hiddeninput.exe` to Resources/bin
c0f8a63 Fix CS and typos
26c35e0 Skip askHiddenResponse test on windows
e2eaf5a Update Changelog, add Readme note about hidden input third party
ac01d5d Fix tests and CS
e396edb [Console] Add DialogHelper::askHiddenResponse method

Discussion
----------

[Console] Add DialogHelper::askHiddenResponse method

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

It adds a method to `DialogHelper` to ask a question and hide the response. It's pretty cool when working with passwords.

This code is more than largely inspired by Composer, see [ConsoleIO.php at line 140](https://github.com/composer/composer/blob/master/src/Composer/IO/ConsoleIO.php#L140)

 You will notice that this PR embeds a Windows Executable binary for windows support. This windows binary is provided by @Seldaek (see https://github.com/Seldaek/hidden-input)
This dependency is not yet available via composer.

If this is a problem to embed this file, we can think of other way to provide this support (make a package from HiddenInput and add composer recommandation for example).

---------------------------------------------------------------------------

by stof at 2012-10-11T17:20:11Z

The link to the hiddeninput source code should be added in the readme.
And you should also update the changelog.

Btw, adding composer for hiddeninput does not make sense. Compsoer is about installing PHP code, not about downloading the source of a C++ program.

---------------------------------------------------------------------------

by romainneutron at 2012-10-11T17:22:58Z

This proposition comes from a discussion I had with Jordi , nothing more :)

Romain

On 11 oct. 2012, at 19:20, Christophe Coevoet <notifications@github.com>
wrote:

The link to the hiddeninput source code should be added in the readme.
And you should also update the changelog.

Btw, adding composer for hiddeninput does not make sense. Compsoer is about
installing PHP code, not about downloading the source of a C++ program.

—
Reply to this email directly or view it on
GitHub<#5731 (comment)>.

---------------------------------------------------------------------------

by romainneutron at 2012-10-12T07:33:00Z

Changelog updated, Readme note added, CS fixed

---------------------------------------------------------------------------

by stof at 2012-10-13T22:09:24Z

the missing point is now the PR to the doc for this new feature

---------------------------------------------------------------------------

by romainneutron at 2012-10-16T00:33:59Z

@stof documentation added

---------------------------------------------------------------------------

by romainneutron at 2012-10-16T09:10:35Z

@fabpot what you asked is now fixed
7a5355c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
1  src/Symfony/Component/Console/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
-----
* added support for colorization on Windows via ConEmu
+ * add a method to Dialog Helper to ask for a question and hide the response
2.1.0
-----
View
187 src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -21,6 +21,8 @@
class DialogHelper extends Helper
{
private $inputStream;
+ private static $shell;
+ private static $stty;
/**
* Asks a question to the user.
@@ -72,6 +74,76 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
}
/**
+ * Asks a question to the user, the response is hidden
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The answer
+ *
+ * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
+ */
+ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $exe = __DIR__ . '/../../Resources/bin/hiddeninput.exe';
+
+ // handle code running from a phar
+ if ('phar:' === substr(__FILE__, 0, 5)) {
+ $tmpExe = sys_get_temp_dir() . '/../../Resources/bin/hiddeninput.exe';
+ copy($exe, $tmpExe);
+ $exe = $tmpExe;
+ }
+
+ $output->write($question);
+ $value = rtrim(shell_exec($exe));
+ $output->writeln('');
+
+ if (isset($tmpExe)) {
+ unlink($tmpExe);
+ }
+
+ return $value;
+ }
+
+ if ($this->hasSttyAvailable()) {
+ $output->write($question);
@fabpot Owner
fabpot added a note

this blank line should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ $sttyMode = shell_exec('/usr/bin/env stty -g');
+
+ shell_exec('/usr/bin/env stty -echo');
+ $value = fgets($this->inputStream ?: STDIN, 4096);
+ shell_exec(sprintf('/usr/bin/env stty %s', $sttyMode));
+
+ if (false === $value) {
+ throw new \RuntimeException('Aborted');
+ }
+
+ $value = trim($value);
+ $output->writeln('');
+
+ return $value;
+ }
+
+ if (false !== $shell = $this->getShell()) {
+ $output->write($question);
@fabpot Owner
fabpot added a note

this blank line should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read mypassword';
+ $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
+ $value = rtrim(shell_exec($command));
+ $output->writeln('');
+
+ return $value;
+ }
+
+ if ($fallback) {
+ return $this->ask($output, $question);
+ }
+
+ throw new \RuntimeException('Unable to hide the response');
+ }
+
+ /**
* Asks for a value and validates the response.
*
* The validator receives the data to validate. It must return the
@@ -80,7 +152,7 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
*
* @param OutputInterface $output An Output instance
* @param string|array $question The question to ask
- * @param callback $validator A PHP callback
+ * @param callable $validator A PHP callback
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param string $default The default answer if none is given by the user
*
@@ -90,21 +162,43 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
*/
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null)
{
- $error = null;
- while (false === $attempts || $attempts--) {
- if (null !== $error) {
- $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
- }
+ $that = $this;
- $value = $this->ask($output, $question, $default);
+ $interviewer = function() use ($output, $question, $default, $that) {
+ return $that->ask($output, $question, $default);
+ };
- try {
- return call_user_func($validator, $value);
- } catch (\Exception $error) {
- }
- }
+ return $this->validateAttempts($interviewer, $output, $validator, $attempts);
+ }
- throw $error;
+ /**
+ * Asks for a value, hide and validates the response.
+ *
+ * The validator receives the data to validate. It must return the
+ * validated data when the data is valid and throw an exception
+ * otherwise.
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question to ask
+ * @param callable $validator A PHP callback
+ * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
+ * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+ *
+ * @return string The response
+ *
+ * @throws \Exception When any of the validators return an error
+ * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
+ *
+ */
+ public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
+ {
+ $that = $this;
+
+ $interviewer = function() use ($output, $question, $fallback, $that) {
+ return $that->askHiddenResponse($output, $question, $fallback);
+ };
+
+ return $this->validateAttempts($interviewer, $output, $validator, $attempts);
}
/**
@@ -136,4 +230,71 @@ public function getName()
{
return 'dialog';
}
+
+ /**
+ * Return a valid unix shell
+ *
+ * @return string|false The valid shell name, false in case no valid shell is found
+ */
+ private function getShell()
+ {
+ if (null !== self::$shell) {
+ return self::$shell;
+ }
+
+ self::$shell = false;
+
+ if (file_exists('/usr/bin/env')) {
+ // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
+ $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
+ foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
+ if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
+ self::$shell = $sh;
+ break;
+ }
+ }
+ }
+
+ return self::$shell;
+ }
+
+ private function hasSttyAvailable()
+ {
+ if (null !== self::$stty) {
+ return self::$stty;
+ }
+
+ exec('/usr/bin/env stty', $output, $exicode);
+
+ return self::$stty = $exicode === 0;
+ }
+
+ /**
+ * Validate an attempt
+ *
+ * @param callable $interviewer A callable that will ask for a question and return the result
+ * @param OutputInterface $output An Output instance
+ * @param callable $validator A PHP callback
+ * @param integer $attempts Max number of times to ask before giving up ; false will ask infinitely
+ *
+ * @return string The validated response
+ *
+ * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
+ */
+ private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts)
+ {
+ $error = null;
+ while (false === $attempts || $attempts--) {
+ if (null !== $error) {
+ $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
+ }
+
+ try {
+ return call_user_func($validator, $interviewer());
+ } catch (\Exception $error) {
+ }
+ }
+
+ throw $error;
+ }
}
View
8 src/Symfony/Component/Console/README.md
@@ -41,12 +41,18 @@ output abstractions (so that you can easily unit-test your commands),
validation, automatic help messages, ...
Tests
----------
+-----
You can run the unit tests with the following command:
phpunit
+Third Party
+-----------
+
+`Resources/bin/hiddeninput.exe` is a third party binary provided within this
+component. Find sources and license at https://github.com/Seldaek/hidden-input.
+
Resources
---------
View
BIN  src/Symfony/Component/Console/Resources/bin/hiddeninput.exe
Binary file not shown
View
13 src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
@@ -31,6 +31,19 @@ public function testAsk()
$this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
}
+ public function testAskHiddenResponse()
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $this->markTestSkipped('This test is not supported on Windows');
+ }
+
+ $dialog = new DialogHelper();
+
+ $dialog->setInputStream($this->getInputStream("8AM\n"));
+
+ $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?'));
+ }
@stof Collaborator
stof added a note

This test should be skipped on Windows as it would trigger the binary, which will not work.

@romainneutron Collaborator

It's fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
public function testAskConfirmation()
{
$dialog = new DialogHelper();
Something went wrong with that request. Please try again.