class QuestionHelper in Zircon Profile 8
Same name and namespace in other branches
- 8.0 vendor/symfony/console/Helper/QuestionHelper.php \Symfony\Component\Console\Helper\QuestionHelper
The QuestionHelper class provides helpers to interact with the user.
@author Fabien Potencier <fabien@symfony.com>
Hierarchy
- class \Symfony\Component\Console\Helper\Helper implements HelperInterface
- class \Symfony\Component\Console\Helper\QuestionHelper
Expanded class hierarchy of QuestionHelper
2 files declare their use of QuestionHelper
- Application.php in vendor/
symfony/ console/ Application.php - QuestionHelperTest.php in vendor/
symfony/ console/ Tests/ Helper/ QuestionHelperTest.php
File
- vendor/
symfony/ console/ Helper/ QuestionHelper.php, line 26
Namespace
Symfony\Component\Console\HelperView source
class QuestionHelper extends Helper {
private $inputStream;
private static $shell;
private static $stty;
/**
* Asks a question to the user.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
* @param Question $question The question to ask
*
* @return string The user answer
*
* @throws \RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question) {
if ($output instanceof ConsoleOutputInterface) {
$output = $output
->getErrorOutput();
}
if (!$input
->isInteractive()) {
return $question
->getDefault();
}
if (!$question
->getValidator()) {
return $this
->doAsk($output, $question);
}
$that = $this;
$interviewer = function () use ($output, $question, $that) {
return $that
->doAsk($output, $question);
};
return $this
->validateAttempts($interviewer, $output, $question);
}
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*
* @throws \InvalidArgumentException In case the stream is not a resource
*/
public function setInputStream($stream) {
if (!is_resource($stream)) {
throw new \InvalidArgumentException('Input stream must be a valid resource.');
}
$this->inputStream = $stream;
}
/**
* Returns the helper's input stream.
*
* @return resource
*/
public function getInputStream() {
return $this->inputStream;
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'question';
}
/**
* Asks the question to the user.
*
* This method is public for PHP 5.3 compatibility, it should be private.
*
* @param OutputInterface $output
* @param Question $question
*
* @return bool|mixed|null|string
*
* @throws \Exception
* @throws \RuntimeException
*/
public function doAsk(OutputInterface $output, Question $question) {
$this
->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question
->getAutocompleterValues();
if (null === $autocomplete || !$this
->hasSttyAvailable()) {
$ret = false;
if ($question
->isHidden()) {
try {
$ret = trim($this
->getHiddenResponse($output, $inputStream));
} catch (\RuntimeException $e) {
if (!$question
->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new \RuntimeException('Aborted');
}
$ret = trim($ret);
}
}
else {
$ret = trim($this
->autocomplete($output, $question, $inputStream));
}
$ret = strlen($ret) > 0 ? $ret : $question
->getDefault();
if ($normalizer = $question
->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* Outputs the question prompt.
*
* @param OutputInterface $output
* @param Question $question
*/
protected function writePrompt(OutputInterface $output, Question $question) {
$message = $question
->getQuestion();
if ($question instanceof ChoiceQuestion) {
$width = max(array_map('strlen', array_keys($question
->getChoices())));
$messages = (array) $question
->getQuestion();
foreach ($question
->getChoices() as $key => $value) {
$messages[] = sprintf(" [<info>%-{$width}s</info>] %s", $key, $value);
}
$output
->writeln($messages);
$message = $question
->getPrompt();
}
$output
->write($message);
}
/**
* Outputs an error message.
*
* @param OutputInterface $output
* @param \Exception $error
*/
protected function writeError(OutputInterface $output, \Exception $error) {
if (null !== $this
->getHelperSet() && $this
->getHelperSet()
->has('formatter')) {
$message = $this
->getHelperSet()
->get('formatter')
->formatBlock($error
->getMessage(), 'error');
}
else {
$message = '<error>' . $error
->getMessage() . '</error>';
}
$output
->writeln($message);
}
/**
* Autocompletes a question.
*
* @param OutputInterface $output
* @param Question $question
*
* @return string
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream) {
$autocomplete = $question
->getAutocompleterValues();
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output
->getFormatter()
->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// Backspace Character
if ("" === $c) {
if (0 === $numMatches && 0 !== $i) {
--$i;
// Move cursor backwards
$output
->write("\33[1D");
}
if ($i === 0) {
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
}
else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = substr($ret, 0, $i);
}
elseif ("\33" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += 'A' === $c[2] ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
}
elseif (ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
// Echo out remaining chars for current match
$output
->write(substr($ret, $i));
$i = strlen($ret);
}
if ("\n" === $c) {
$output
->write($c);
break;
}
$numMatches = 0;
}
continue;
}
else {
$output
->write($c);
$ret .= $c;
++$i;
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $ret) && $i !== strlen($value)) {
$matches[$numMatches++] = $value;
}
}
}
// Erase characters from cursor to end of line
$output
->write("\33[K");
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output
->write("\0337");
// Write highlighted text
$output
->write('<hl>' . substr($matches[$ofs], $i) . '</hl>');
// Restore cursor position
$output
->write("\338");
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
}
/**
* Gets a hidden response from user.
*
* @param OutputInterface $output An Output instance
*
* @return string The answer
*
* @throws \RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream) {
if ('\\' === DIRECTORY_SEPARATOR) {
$exe = __DIR__ . '/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir() . '/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$value = rtrim(shell_exec($exe));
$output
->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if ($this
->hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new \RuntimeException('Aborted');
}
$value = trim($value);
$output
->writeln('');
return $value;
}
if (false !== ($shell = $this
->getShell())) {
$readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r 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;
}
throw new \RuntimeException('Unable to hide the response.');
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param Question $question A Question instance
*
* @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, Question $question) {
$error = null;
$attempts = $question
->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this
->writeError($output, $error);
}
try {
return call_user_func($question
->getValidator(), $interviewer());
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool 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;
}
/**
* Returns whether Stty is available or not.
*
* @return bool
*/
private function hasSttyAvailable() {
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = $exitcode === 0;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Helper:: |
protected | property | ||
Helper:: |
public static | function | ||
Helper:: |
public static | function | ||
Helper:: |
public | function |
Gets the helper set associated with this helper. Overrides HelperInterface:: |
|
Helper:: |
public | function |
Sets the helper set associated with this helper. Overrides HelperInterface:: |
|
Helper:: |
public static | function | Returns the length of a string, using mb_strwidth if it is available. | |
Helper:: |
public static | function | ||
QuestionHelper:: |
private | property | ||
QuestionHelper:: |
private static | property | ||
QuestionHelper:: |
private static | property | ||
QuestionHelper:: |
public | function | Asks a question to the user. | 1 |
QuestionHelper:: |
private | function | Autocompletes a question. | |
QuestionHelper:: |
public | function | Asks the question to the user. | |
QuestionHelper:: |
private | function | Gets a hidden response from user. | |
QuestionHelper:: |
public | function | Returns the helper's input stream. | |
QuestionHelper:: |
public | function |
Returns the canonical name of this helper. Overrides HelperInterface:: |
|
QuestionHelper:: |
private | function | Returns a valid unix shell. | |
QuestionHelper:: |
private | function | Returns whether Stty is available or not. | |
QuestionHelper:: |
public | function | Sets the input stream to read from when interacting with the user. | |
QuestionHelper:: |
private | function | Validates an attempt. | |
QuestionHelper:: |
protected | function | Outputs an error message. | 1 |
QuestionHelper:: |
protected | function | Outputs the question prompt. | 1 |