From c24c6968af8f650f47d8877256ca43d782668df1 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 | 24 ++++++ .../Console/Helper/QuestionHelper.php | 85 ++++++++++++------- .../Tests/Helper/QuestionHelperTest.php | 6 +- .../phpt/uses_stdin_as_interactive_input.phpt | 4 +- 4 files changed, 82 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..eda469428cdb2 --- /dev/null +++ b/src/Symfony/Component/Console/Exception/MissingInputException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +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