From 8ddaa20b29caa7aeb505681f12e86a7a444ae8b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?=
Date: Wed, 11 Mar 2020 19:41:03 +0100
Subject: [PATCH] [Console] Fallback to default answers when unable to read
input
---
.../Exception/MissingInputException.php | 21 +++++
.../Console/Helper/QuestionHelper.php | 85 ++++++++++++-------
.../Tests/Helper/QuestionHelperTest.php | 6 +-
.../phpt/uses_stdin_as_interactive_input.phpt | 4 +-
4 files changed, 79 insertions(+), 37 deletions(-)
create mode 100644 src/Symfony/Component/Console/Exception/MissingInputException.php
diff --git a/src/Symfony/Component/Console/Exception/MissingInputException.php b/src/Symfony/Component/Console/Exception/MissingInputException.php
new file mode 100644
index 0000000000000..04f02ade45001
--- /dev/null
+++ b/src/Symfony/Component/Console/Exception/MissingInputException.php
@@ -0,0 +1,21 @@
+
+ *
+ * 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ý
+ */
+class MissingInputException extends RuntimeException implements ExceptionInterface
+{
+}
diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index 40709cedd929d..b383252c5a3bc 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Console\Helper;
+use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@@ -48,44 +49,32 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu
}
if (!$input->isInteractive()) {
- $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;
+ return $this->getDefaultAnswer($question);
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
- if (!$question->getValidator()) {
- return $this->doAsk($output, $question);
- }
+ try {
+ if (!$question->getValidator()) {
+ return $this->doAsk($output, $question);
+ }
- $interviewer = function () use ($output, $question) {
- return $this->doAsk($output, $question);
- };
+ $interviewer = function () use ($output, $question) {
+ return $this->doAsk($output, $question);
+ };
- 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 @@ private function doAsk(OutputInterface $output, Question $question)
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
- throw new RuntimeException('Aborted.');
+ throw new MissingInputException('Aborted.');
}
if ($question->isTrimmable()) {
$ret = trim($ret);
@@ -158,6 +147,36 @@ private function doAsk(OutputInterface $output, Question $question)
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.
*/
@@ -240,7 +259,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
// 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())) {
shell_exec(sprintf('stty %s', $sttyMode));
- throw new RuntimeException('Aborted.');
+ throw new MissingInputException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
@@ -406,7 +425,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
- throw new RuntimeException('Aborted.');
+ throw new MissingInputException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
index fcba3b3b2fd19..461318efe7397 100644
--- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
@@ -696,7 +696,7 @@ public function testChoiceOutputFormattingQuestionForUtf8Keys()
public function testAskThrowsExceptionOnMissingInput()
{
- $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
+ $this->expectException('Symfony\Component\Console\Exception\MissingInputException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
@@ -704,7 +704,7 @@ public function testAskThrowsExceptionOnMissingInput()
public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
{
- $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
+ $this->expectException('Symfony\Component\Console\Exception\MissingInputException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
@@ -712,7 +712,7 @@ public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
public function testAskThrowsExceptionOnMissingInputWithValidator()
{
- $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
+ $this->expectException('Symfony\Component\Console\Exception\MissingInputException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
diff --git a/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt b/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt
index db1bb4ce436e2..3f329cc73f805 100644
--- a/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt
+++ b/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt
@@ -18,7 +18,8 @@ require $vendor.'/vendor/autoload.php';
(new Application())
->register('app')
->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()
->setDefaultCommand('app', true)
@@ -26,3 +27,4 @@ require $vendor.'/vendor/autoload.php';
;
--EXPECT--
Foo?Hello World
+Bar?bar