feature #34790 [Console] Remove restriction for choices to be strings (LordZardeck, YaFou, ogizanagi)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Console] Remove restriction for choices to be strings

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #34789
| License       | MIT

When using choice, selected answers are forced into strings, preventing us from using complex values such as a class with a custom __toString. This is a problem, as I need the ability to present the user with a list of display strings to choose from, but need the ID associated with that display string in order to do anything useful.

Commits
-------

d276cc9ca3 [Console] Cast associative choices questions keys to string
a0223088a0 [Console] Add tests for removing restriction for choices to be strings
3349d3ce89 Remove restriction for choices to be strings
This commit is contained in:
Fabien Potencier 2020-08-31 17:31:17 +02:00
commit d6468a9634
3 changed files with 74 additions and 36 deletions

View File

@ -169,7 +169,8 @@ class ChoiceQuestion extends Question
throw new InvalidArgumentException(sprintf($errorMessage, $value));
}
$multiselectChoices[] = (string) $result;
// For associative choices, consistently return the key as string:
$multiselectChoices[] = $isAssoc ? (string) $result : $result;
}
if ($multiselect) {

View File

@ -607,41 +607,6 @@ EOD;
];
}
/**
* @dataProvider mixedKeysChoiceListAnswerProvider
*/
public function testChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue)
{
$possibleChoices = [
'0' => 'No environment',
'1' => 'My environment 1',
'env_2' => 'My environment 2',
3 => 'My environment 3',
];
$dialog = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
$question->setMaxAttempts(1);
$answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question);
$this->assertSame($expectedValue, $answer);
}
public function mixedKeysChoiceListAnswerProvider()
{
return [
['0', '0'],
['No environment', '0'],
['1', '1'],
['env_2', 'env_2'],
[3, '3'],
['My environment 1', '1'],
];
}
/**
* @dataProvider answerProvider
*/

View File

@ -59,6 +59,18 @@ class ChoiceQuestionTest extends TestCase
['First response', 'Second response'],
'When passed multiple answers on MultiSelect, the defaultValidator must return these answers as an array',
],
[
false,
[0],
'First response',
'When passed single answer using choice\'s key, the defaultValidator must return the choice value',
],
[
true,
['0, 2'],
['First response', 'Third response'],
'When passed multiple answers using choices\' key, the defaultValidator must return the choice values in an array',
],
];
}
@ -77,4 +89,64 @@ class ChoiceQuestionTest extends TestCase
$this->assertSame(['First response ', ' Second response'], $question->getValidator()('First response , Second response'));
}
/**
* @dataProvider selectAssociativeChoicesProvider
*/
public function testSelectAssociativeChoices($providedAnswer, $expectedValue)
{
$question = new ChoiceQuestion('A question', [
'0' => 'First choice',
'foo' => 'Foo',
'99' => 'N°99',
'string object' => new StringChoice('String Object'),
]);
$this->assertSame($expectedValue, $question->getValidator()($providedAnswer));
}
public function selectAssociativeChoicesProvider()
{
return [
'select "0" choice by key' => ['0', '0'],
'select "0" choice by value' => ['First choice', '0'],
'select by key' => ['foo', 'foo'],
'select by value' => ['Foo', 'foo'],
'select by key, with numeric key' => ['99', '99'],
'select by value, with numeric key' => ['N°99', '99'],
'select by key, with string object value' => ['string object', 'string object'],
'select by value, with string object value' => ['String Object', 'string object'],
];
}
public function testSelectWithNonStringChoices()
{
$question = new ChoiceQuestion('A question', [
$result1 = new StringChoice('foo'),
$result2 = new StringChoice('bar'),
$result3 = new StringChoice('baz'),
]);
$this->assertSame($result1, $question->getValidator()('foo'), 'answer can be selected by its string value');
$this->assertSame($result1, $question->getValidator()(0), 'answer can be selected by index');
$question->setMultiselect(true);
$this->assertSame([$result3, $result2], $question->getValidator()('baz, bar'));
}
}
class StringChoice
{
private $string;
public function __construct(string $string)
{
$this->string = $string;
}
public function __toString()
{
return $this->string;
}
}