8000 [OptionsResolver] Support union of types · symfony/symfony@69ec31d · GitHub
[go: up one dir, main page]

Skip to content

Commit 69ec31d

Browse files
VincentLangletfabpot
authored andcommitted
[OptionsResolver] Support union of types
1 parent 04ee771 c
8000
ommit 69ec31d

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,8 +1139,28 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
11391139
return $value;
11401140
}
11411141

1142-
private function verifyTypes(string $type, mixed $value, array &$invalidTypes, int $level = 0): bool
1142+
private function verifyTypes(string $type, mixed $value, ?array &$invalidTypes = null, int $level = 0): bool
11431143
{
1144+
$allowedTypes = $this->splitOutsideParenthesis($type);
1145+
if (\count($allowedTypes) > 1) {
1146+
foreach ($allowedTypes as $allowedType) {
1147+
if ($this->verifyTypes($allowedType, $value)) {
1148+
return true;
1149+
}
1150+
}
1151+
1152+
if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
1153+
$invalidTypes[get_debug_type($value)] = true;
1154+
}
1155+
1156+
return false;
1157+
}
1158+
1159+
$type = $allowedTypes[0];
1160+
if (str_starts_with($type, '(') && str_ends_with($type, ')')) {
1161+
return $this->verifyTypes(substr($type, 1, -1), $value, $invalidTypes, $level);
1162+
}
1163+
11441164
if (\is_array($value) && str_ends_with($type, '[]')) {
11451165
$type = substr($type, 0, -2);
11461166
$valid = true;
@@ -1158,13 +1178,47 @@ private function verifyTypes(string $type, mixed $value, array &$invalidTypes, i
11581178
return true;
11591179
}
11601180

1161-
if (!$invalidTypes || $level > 0) {
1181+
if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
11621182
$invalidTypes[get_debug_type($value)] = true;
11631183
}
11641184

11651185
return false;
11661186
}
11671187

1188+
/**
1189+
* @return list<string>
1190+
*/
1191+
private function splitOutsideParenthesis(string $type): array
1192+
{
1193+
$parts = [];
1194+
$currentPart = '';
1195+
$parenthesisLevel = 0;
1196+
1197+
$typeLength = \strlen($type);
1198+
for ($i = 0; $i < $typeLength; ++$i) {
1199+
$char = $type[$i];
1200+
1201+
if ('(' === $char) {
1202+
++$parenthesisLevel;
1203+
} elseif (')' === $char) {
1204+
--$parenthesisLevel;
1205+
}
1206+
1207+
if ('|' === $char && 0 === $parenthesisLevel) {
1208+
$parts[] = $currentPart;
1209+
$currentPart = '';
1210+
} else {
1211+
$currentPart .= $char;
1212+
}
1213+
}
1214+
1215+
if ('' !== $currentPart) {
1216+
$parts[] = $currentPart;
1217+
}
1218+
1219+
return $parts;
1220+
}
1221+
11681222
/**
11691223
* Returns whether a resolved option with the given name exists.
11701224
*

src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,44 @@ public function testSetAllowedTypesFailsIfUnknownOption()
778778
$this->resolver->setAllowedTypes('foo', 'string');
779779
}
780780

781+
public function testResolveTypedWithUnion()
782+
{
783+
$this->resolver->setDefined('foo');
784+
$this->resolver->setAllowedTypes('foo', 'string|int');
785+
786+
$options = $this->resolver->resolve(['foo' => 1]);
787+
$this->assertSame(['foo' => 1], $options);
788+
789+
$options = $this->resolver->resolve(['foo' => '1']);
790+
$this->assertSame(['foo' => '1'], $options);
791+
}
792+
793+
public function testResolveTypedWithUnionOfClasse()
794+
{
795+
$this->resolver->setDefined('foo');
796+
$this->resolver->setAllowedTypes('foo', \DateTime::class.'|'.\DateTimeImmutable::class);
797+
798+
$datetime = new \DateTime();
799+
$options = $this->resolver->resolve(['foo' => $datetime]);
800+
$this->assertSame(['foo' => $datetime], $options);
801+
802+
$datetime = new \DateTimeImmutable();
803+
$options = $this->resolver->resolve(['foo' => $datetime]);
804+
$this->assertSame(['foo' => $datetime], $options);
805+
}
806+
807+
public function testResolveTypedWithUnionOfArray()
808+
{
809+
$this->resolver->setDefined('foo');
810+
$this->resolver->setAllowedTypes('foo', '(string|int)[]|(bool|int)[]');
811+
812+
$options = $this->resolver->resolve(['foo' => [1, '1']]);
813+
$this->assertSame(['foo' => [1, '1']], $options);
814+
815+
$options = $this->resolver->resolve(['foo' => [1, true]]);
816+
$this->assertSame(['foo' => [1, true]], $options);
817+
}
818+
781819
public function testResolveTypedArray()
782820
{
783821
$this->resolver->setDefined('foo');
@@ -787,6 +825,15 @@ public function testResolveTypedArray()
787825
$this->assertSame(['foo' => ['bar', 'baz']], $options);
788826
}
789827

828+
public function testResolveTypedArrayWithUnion()
829+
{
830+
$this->resolver->setDefined('foo');
831+
$this->resolver->setAllowedTypes('foo', '(string|int)[]');
832+
$options = $this->resolver->resolve(['foo' => ['bar', 1]]);
833+
834+
$this->assertSame(['foo' => ['bar', 1]], $options);
835+
}
836+
790837
public function testFailIfSetAllowedTypesFromLazyOption()
791838
{
792839
$this->expectException(AccessException::class);
@@ -878,6 +925,7 @@ public static function provideInvalidTypes()
878925
[[null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "null".'],
879926
[['string', null], ['string[]', 'string'], 'The option "option" with value array is expected to be of type "string[]" or "string", but one of the elements is of type "null".'],
880927
[[\stdClass::class], ['string'], 'The option "option" with value array is expected to be of type "string", but is of type "array".'],
928+
[['foo', 12], '(string|bool)[]', 'The option "option" with value array is expected to be of type "(string|bool)[]", but one of the elements is of type "int".'],
881929
];
882930
}
883931

@@ -1903,6 +1951,26 @@ public function testNestedArrays()
19031951
]));
19041952
}
19051953

1954+
public function testNestedArraysWithUnions()
1955+
{
1956+
$this->resolver->setDefined('foo');
1957+
$this->resolver->setAllowedTypes('foo', '(int|float|(int|float)[])[]');
1958+
1959+
$this->assertEquals([
1960+
'foo' => [
1961+
1,
1962+
2.0,
1963+
[1, 2.0],
1964+
],
1965+
], $this->resolver->resolve([
1966+
'foo' => [
1967+
1,
1968+
2.0,
1969+
[1, 2.0],
1970+
],
1971+
]));
1972+
}
1973+
19061974
public function testNested2Arrays()
19071975
{
19081976
$this->resolver->setDefined('foo');

0 commit comments

Comments
 (0)
0