Skip to content

Commit bd1aaf1

Browse files
committed
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 #36027. This fixes linked issues without having to revert fix for #30726. Successfully tested with composer script, `docker run` and `docker run -it`. Commits ------- 8ddaa20 [Console] Fallback to default answers when unable to read input
2 parents 3543bf6 + 8ddaa20 commit bd1aaf1

File tree

4 files changed

+79
-37
lines changed

4 files changed

+79
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Exception;
13+
14+
/**
15+
* Represents failure to read input from stdin.
16+
*
17+
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
18+
*/
19+
class MissingInputException extends RuntimeException implements ExceptionInterface
20+
{
21+
}

src/Symfony/Component/Console/Helper/QuestionHelper.php

+52-33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Console\Helper;
1313

14+
use Symfony\Component\Console\Exception\MissingInputException;
1415
use Symfony\Component\Console\Exception\RuntimeException;
1516
use Symfony\Component\Console\Formatter\OutputFormatter;
1617
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@@ -48,44 +49,32 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu
4849
}
4950

5051
if (!$input->isInteractive()) {
51-
$default = $question->getDefault();
52-
53-
if (null === $default) {
54-
return $default;
55-
}
56-
57-
if ($validator = $question->getValidator()) {
58-
return \call_user_func($question->getValidator(), $default);
59-
} elseif ($question instanceof ChoiceQuestion) {
60-
$choices = $question->getChoices();
61-
62-
if (!$question->isMultiselect()) {
63-
return isset($choices[$default]) ? $choices[$default] : $default;
64-
}
65-
66-
$default = explode(',', $default);
67-
foreach ($default as $k => $v) {
68-
$v = $question->isTrimmable() ? trim($v) : $v;
69-
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
70-
}
71-
}
72-
73-
return $default;
52+
return $this->getDefaultAnswer($question);
7453
}
7554

7655
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
7756
$this->inputStream = $stream;
7857
}
7958

80-
if (!$question->getValidator()) {
81-
return $this->doAsk($output, $question);
82-
}
59+
try {
60+
if (!$question->getValidator()) {
61+
return $this->doAsk($output, $question);
62+
}
8363

84-
$interviewer = function () use ($output, $question) {
85-
return $this->doAsk($output, $question);
86-
};
64+
$interviewer = function () use ($output, $question) {
65+
return $this->doAsk($output, $question);
66+
};
8767

88-
return $this->validateAttempts($interviewer, $output, $question);
68+
return $this->validateAttempts($interviewer, $output, $question);
69+
} catch (MissingInputException $exception) {
70+
$input->setInteractive(false);
71+
72+
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
73+
throw $exception;
74+
}
75+
76+
return $fallbackOutput;
77+
}
8978
}
9079

9180
/**
@@ -134,7 +123,7 @@ private function doAsk(OutputInterface $output, Question $question)
134123
if (false === $ret) {
135124
$ret = fgets($inputStream, 4096);
136125
if (false === $ret) {
137-
throw new RuntimeException('Aborted.');
126+
throw new MissingInputException('Aborted.');
138127
}
139128
if ($question->isTrimmable()) {
140129
$ret = trim($ret);
@@ -158,6 +147,36 @@ private function doAsk(OutputInterface $output, Question $question)
158147
return $ret;
159148
}
160149

150+
/**
151+
* @return mixed
152+
*/
153+
private function getDefaultAnswer(Question $question)
154+
{
155+
$default = $question->getDefault();
156+
157+
if (null === $default) {
158+
return $default;
159+
}
160+
161+
if ($validator = $question->getValidator()) {
162+
return \call_user_func($question->getValidator(), $default);
163+
} elseif ($question instanceof ChoiceQuestion) {
164+
$choices = $question->getChoices();
165+
166+
if (!$question->isMultiselect()) {
167+
return isset($choices[$default]) ? $choices[$default] : $default;
168+
}
169+
170+
$default = explode(',', $default);
171+
foreach ($default as $k => $v) {
172+
$v = $question->isTrimmable() ? trim($v) : $v;
173+
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
174+
}
175+
}
176+
177+
return $default;
178+
}
179+
161180
/**
162181
* Outputs the question prompt.
163182
*/
@@ -240,7 +259,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu
240259
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
241260
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
242261
shell_exec(sprintf('stty %s', $sttyMode));
243-
throw new RuntimeException('Aborted.');
262+
throw new MissingInputException('Aborted.');
244263
} elseif ("\177" === $c) { // Backspace Character
245264
if (0 === $numMatches && 0 !== $i) {
246265
--$i;
@@ -406,7 +425,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $
406425
shell_exec(sprintf('stty %s', $sttyMode));
407426

408427
if (false === $value) {
409-
throw new RuntimeException('Aborted.');
428+
throw new MissingInputException('Aborted.');
410429
}
411430
if ($trimmable) {
412431
$value = trim($value);

src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -696,23 +696,23 @@ public function testChoiceOutputFormattingQuestionForUtf8Keys()
696696

697697
public function testAskThrowsExceptionOnMissingInput()
698698
{
699-
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
699+
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
700700
$this->expectExceptionMessage('Aborted.');
701701
$dialog = new QuestionHelper();
702702
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
703703
}
704704

705705
public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
706706
{
707-
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
707+
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
708708
$this->expectExceptionMessage('Aborted.');
709709
$dialog = new QuestionHelper();
710710
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
711711
}
712712

713713
public function testAskThrowsExceptionOnMissingInputWithValidator()
714714
{
715-
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
715+
$this->expectException('Symfony\Component\Console\Exception\MissingInputException');
716716
$this->expectExceptionMessage('Aborted.');
717717
$dialog = new QuestionHelper();
718718

src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ require $vendor.'/vendor/autoload.php';
1818
(new Application())
1919
->register('app')
2020
->setCode(function(InputInterface $input, OutputInterface $output) {
21-
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Foo?')));
21+
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Foo?', 'foo')));
22+
$output->writeln((new QuestionHelper())->ask($input, $output, new Question('Bar?', 'bar')));
2223
})
2324
->getApplication()
2425
->setDefaultCommand('app', true)
2526
->run()
2627
;
2728
--EXPECT--
2829
Foo?Hello World
30+
Bar?bar

0 commit comments

Comments
 (0)