bug #36031 [Console] Fallback to default answers when unable to read input (ostrolucky)
This PR was merged into the 4.4 branch.
Discussion
----------
[Console] Fallback to default answers when unable to read input
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Tickets | Fix #36027, Fix #35988
| License | MIT
| Doc PR |
Alternative to https://github.com/symfony/symfony/pull/36027.
This fixes linked issues without having to revert fix for #30726. Successfully tested with composer script, `docker run` and `docker run -it`.
Commits
-------
8ddaa20b29
[Console] Fallback to default answers when unable to read input
This commit is contained in:
commit
bd1aaf1f98
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Console\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents failure to read input from stdin.
|
||||||
|
*
|
||||||
|
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
|
||||||
|
*/
|
||||||
|
class MissingInputException extends RuntimeException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Console\Helper;
|
namespace Symfony\Component\Console\Helper;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Exception\MissingInputException;
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
@ -48,35 +49,14 @@ class QuestionHelper extends Helper
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$input->isInteractive()) {
|
if (!$input->isInteractive()) {
|
||||||
$default = $question->getDefault();
|
return $this->getDefaultAnswer($question);
|
||||||
|
|
||||||
if (null === $default) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validator = $question->getValidator()) {
|
|
||||||
return \call_user_func($question->getValidator(), $default);
|
|
||||||
} elseif ($question instanceof ChoiceQuestion) {
|
|
||||||
$choices = $question->getChoices();
|
|
||||||
|
|
||||||
if (!$question->isMultiselect()) {
|
|
||||||
return isset($choices[$default]) ? $choices[$default] : $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
$default = explode(',', $default);
|
|
||||||
foreach ($default as $k => $v) {
|
|
||||||
$v = $question->isTrimmable() ? trim($v) : $v;
|
|
||||||
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
|
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
|
||||||
$this->inputStream = $stream;
|
$this->inputStream = $stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (!$question->getValidator()) {
|
if (!$question->getValidator()) {
|
||||||
return $this->doAsk($output, $question);
|
return $this->doAsk($output, $question);
|
||||||
}
|
}
|
||||||
@ -86,6 +66,15 @@ class QuestionHelper extends Helper
|
|||||||
};
|
};
|
||||||
|
|
||||||
return $this->validateAttempts($interviewer, $output, $question);
|
return $this->validateAttempts($interviewer, $output, $question);
|
||||||
|
} catch (MissingInputException $exception) {
|
||||||
|
$input->setInteractive(false);
|
||||||
|
|
||||||
|
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallbackOutput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +123,7 @@ class QuestionHelper extends Helper
|
|||||||
if (false === $ret) {
|
if (false === $ret) {
|
||||||
$ret = fgets($inputStream, 4096);
|
$ret = fgets($inputStream, 4096);
|
||||||
if (false === $ret) {
|
if (false === $ret) {
|
||||||
throw new RuntimeException('Aborted.');
|
throw new MissingInputException('Aborted.');
|
||||||
}
|
}
|
||||||
if ($question->isTrimmable()) {
|
if ($question->isTrimmable()) {
|
||||||
$ret = trim($ret);
|
$ret = trim($ret);
|
||||||
@ -158,6 +147,36 @@ class QuestionHelper extends Helper
|
|||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function getDefaultAnswer(Question $question)
|
||||||
|
{
|
||||||
|
$default = $question->getDefault();
|
||||||
|
|
||||||
|
if (null === $default) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validator = $question->getValidator()) {
|
||||||
|
return \call_user_func($question->getValidator(), $default);
|
||||||
|
} elseif ($question instanceof ChoiceQuestion) {
|
||||||
|
$choices = $question->getChoices();
|
||||||
|
|
||||||
|
if (!$question->isMultiselect()) {
|
||||||
|
return isset($choices[$default]) ? $choices[$default] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$default = explode(',', $default);
|
||||||
|
foreach ($default as $k => $v) {
|
||||||
|
$v = $question->isTrimmable() ? trim($v) : $v;
|
||||||
|
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs the question prompt.
|
* Outputs the question prompt.
|
||||||
*/
|
*/
|
||||||
@ -240,7 +259,7 @@ class QuestionHelper extends Helper
|
|||||||
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
||||||
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
|
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
|
||||||
shell_exec(sprintf('stty %s', $sttyMode));
|
shell_exec(sprintf('stty %s', $sttyMode));
|
||||||
throw new RuntimeException('Aborted.');
|
throw new MissingInputException('Aborted.');
|
||||||
} elseif ("\177" === $c) { // Backspace Character
|
} elseif ("\177" === $c) { // Backspace Character
|
||||||
if (0 === $numMatches && 0 !== $i) {
|
if (0 === $numMatches && 0 !== $i) {
|
||||||
--$i;
|
--$i;
|
||||||
@ -406,7 +425,7 @@ class QuestionHelper extends Helper
|
|||||||
shell_exec(sprintf('stty %s', $sttyMode));
|
shell_exec(sprintf('stty %s', $sttyMode));
|
||||||
|
|
||||||
if (false === $value) {
|
if (false === $value) {
|
||||||
throw new RuntimeException('Aborted.');
|
throw new MissingInputException('Aborted.');
|
||||||
}
|
}
|
||||||
if ($trimmable) {
|
if ($trimmable) {
|
||||||
$value = trim($value);
|
$value = trim($value);
|
||||||
|
@ -696,7 +696,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
|
|||||||
|
|
||||||
public function testAskThrowsExceptionOnMissingInput()
|
public function testAskThrowsExceptionOnMissingInput()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
|
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
|
||||||
$this->expectExceptionMessage('Aborted.');
|
$this->expectExceptionMessage('Aborted.');
|
||||||
$dialog = new QuestionHelper();
|
$dialog = new QuestionHelper();
|
||||||
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
|
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
|
||||||
@ -704,7 +704,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
|
|||||||
|
|
||||||
public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
|
public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
|
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
|
||||||
$this->expectExceptionMessage('Aborted.');
|
$this->expectExceptionMessage('Aborted.');
|
||||||
$dialog = new QuestionHelper();
|
$dialog = new QuestionHelper();
|
||||||
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
|
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
|
||||||
@ -712,7 +712,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
|
|||||||
|
|
||||||
public function testAskThrowsExceptionOnMissingInputWithValidator()
|
public function testAskThrowsExceptionOnMissingInputWithValidator()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
|
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
|
||||||
$this->expectExceptionMessage('Aborted.');
|
$this->expectExceptionMessage('Aborted.');
|
||||||
$dialog = new QuestionHelper();
|
$dialog = new QuestionHelper();
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ require $vendor.'/vendor/autoload.php';
|
|||||||
(new Application())
|
(new Application())
|
||||||
->register('app')
|
->register('app')
|
||||||
->setCode(function(InputInterface $input, OutputInterface $output) {
|
->setCode(function(InputInterface $input, OutputInterface $output) {
|
||||||
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Foo?')));
|
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Foo?', 'foo')));
|
||||||
|
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Bar?', 'bar')));
|
||||||
})
|
})
|
||||||
->getApplication()
|
->getApplication()
|
||||||
->setDefaultCommand('app', true)
|
->setDefaultCommand('app', true)
|
||||||
@ -26,3 +27,4 @@ require $vendor.'/vendor/autoload.php';
|
|||||||
;
|
;
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Foo?Hello World
|
Foo?Hello World
|
||||||
|
Bar?bar
|
||||||
|
Reference in New Issue
Block a user