Skip to content

Commit 433e2f9

Browse files
Merge branch '4.1'
* 4.1: [Console] fix CS [OptionResolver] resolve arrays [TwigBridge] Fix missing path and separators in loader paths list on debug:twig output [PropertyInfo] Fix dock block lookup fallback loop [FrameworkBundle] Fixed phpdoc in MicroKernelTrait::configureRoutes() [HttpFoundation] don't encode cookie name for BC improve deprecation messages minor #27858 [Console] changed warning verbosity; fixes typo (adrian-enspired) AppBundle->App. [Workflow] Fixed BC break [DI] Fix dumping ignore-on-uninitialized references to synthetic services
2 parents 72f3c0d + 0aec9f9 commit 433e2f9

File tree

2 files changed

+219
-45
lines changed

2 files changed

+219
-45
lines changed

OptionsResolver.php

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ public function setAllowedValues($option, $allowedValues)
490490
));
491491
}
492492

493-
$this->allowedValues[$option] = is_array($allowedValues) ? $allowedValues : array($allowedValues);
493+
$this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : array($allowedValues);
494494

495495
// Make sure the option is processed
496496
unset($this->resolved[$option]);
@@ -843,14 +843,13 @@ public function offsetGet($option)
843843
}
844844

845845
if (!$valid) {
846-
throw new InvalidOptionsException(sprintf(
847-
'The option "%s" with value %s is expected to be of type '.
848-
'"%s", but is of type "%s".',
849-
$option,
850-
$this->formatValue($value),
851-
implode('" or "', $this->allowedTypes[$option]),
852-
implode('|', array_keys($invalidTypes))
853-
));
846+
$keys = array_keys($invalidTypes);
847+
848+
if (1 === \count($keys) && '[]' === substr($keys[0], -2)) {
849+
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), $keys[0]));
850+
}
851+
852+
throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $this->formatValue($value), implode('" or "', $this->allowedTypes[$option]), implode('|', array_keys($invalidTypes))));
854853
}
855854
}
856855

@@ -939,32 +938,10 @@ public function offsetGet($option)
939938
return $value;
940939
}
941940

942-
/**
943-
* @param string $type
944-
* @param mixed $value
945-
* @param array &$invalidTypes
946-
*
947-
* @return bool
948-
*/
949-
private function verifyTypes($type, $value, array &$invalidTypes)
941+
private function verifyTypes(string $type, $value, array &$invalidTypes): bool
950942
{
951-
if ('[]' === substr($type, -2) && is_array($value)) {
952-
$originalType = $type;
953-
$type = substr($type, 0, -2);
954-
$invalidValues = array_filter( // Filter out valid values, keeping invalid values in the resulting array
955-
$value,
956-
function ($value) use ($type) {
957-
return !self::isValueValidType($type, $value);
958-
}
959-
);
960-
961-
if (!$invalidValues) {
962-
return true;
963-
}
964-
965-
$invalidTypes[$this->formatTypeOf($value, $originalType)] = true;
966-
967-
return false;
943+
if (\is_array($value) && '[]' === substr($type, -2)) {
944+
return $this->verifyArrayType($type, $value, $invalidTypes);
968945
}
969946

970947
if (self::isValueValidType($type, $value)) {
@@ -978,6 +955,43 @@ function ($value) use ($type) {
978955
return false;
979956
}
980957

958+
private function verifyArrayType(string $type, array $value, array &$invalidTypes, int $level = 0): bool
959+
{
960+
$type = substr($type, 0, -2);
961+
962+
$suffix = '[]';
963+
while (\strlen($suffix) <= $level * 2) {
964+
$suffix .= '[]';
965+
}
966+
967+
if ('[]' === substr($type, -2)) {
968+
$success = true;
969+
foreach ($value as $item) {
970+
if (!\is_array($item)) {
971+
$invalidTypes[$this->formatTypeOf($item, null).$suffix] = true;
972+
973+
return false;
974+
}
975+
976+
if (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
977+
$success = false;
978+
}
979+
}
980+
981+
return $success;
982+
}
983+
984+
foreach ($value as $item) {
985+
if (!self::isValueValidType($type, $item)) {
986+
$invalidTypes[$this->formatTypeOf($item, $type).$suffix] = $value;
987+
988+
return false;
989+
}
990+
}
991+
992+
return true;
993+
}
994+
981995
/**
982996
* Returns whether a resolved option with the given name exists.
983997
*
@@ -1058,13 +1072,13 @@ private function formatTypeOf($value, ?string $type): string
10581072
while ('[]' === substr($type, -2)) {
10591073
$type = substr($type, 0, -2);
10601074
$value = array_shift($value);
1061-
if (!is_array($value)) {
1075+
if (!\is_array($value)) {
10621076
break;
10631077
}
10641078
$suffix .= '[]';
10651079
}
10661080

1067-
if (is_array($value)) {
1081+
if (\is_array($value)) {
10681082
$subTypes = array();
10691083
foreach ($value as $val) {
10701084
$subTypes[$this->formatTypeOf($val, null)] = true;
@@ -1074,7 +1088,7 @@ private function formatTypeOf($value, ?string $type): string
10741088
}
10751089
}
10761090

1077-
return (is_object($value) ? get_class($value) : gettype($value)).$suffix;
1091+
return (\is_object($value) ? get_class($value) : gettype($value)).$suffix;
10781092
}
10791093

10801094
/**
@@ -1088,19 +1102,19 @@ private function formatTypeOf($value, ?string $type): string
10881102
*/
10891103
private function formatValue($value): string
10901104
{
1091-
if (is_object($value)) {
1105+
if (\is_object($value)) {
10921106
return get_class($value);
10931107
}
10941108

1095-
if (is_array($value)) {
1109+
if (\is_array($value)) {
10961110
return 'array';
10971111
}
10981112

1099-
if (is_string($value)) {
1113+
if (\is_string($value)) {
11001114
return '"'.$value.'"';
11011115
}
11021116

1103-
if (is_resource($value)) {
1117+
if (\is_resource($value)) {
11041118
return 'resource';
11051119
}
11061120

@@ -1136,8 +1150,21 @@ private function formatValues(array $values): string
11361150
return implode(', ', $values);
11371151
}
11381152

1139-
private static function isValueValidType($type, $value)
1153+
private static function isValueValidType(string $type, $value): bool
11401154
{
11411155
return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type;
11421156
}
1157+
1158+
private function getInvalidValues(array $arrayValues, string $type): array
1159+
{
1160+
$invalidValues = array();
1161+
1162+
foreach ($arrayValues as $key => $value) {
1163+
if (!self::isValueValidType($type, $value)) {
1164+
$invalidValues[$key] = $value;
1165+
}
1166+
}
1167+
1168+
return $invalidValues;
1169+
}
11431170
}

Tests/OptionsResolverTest.php

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ public function testFailIfSetAllowedTypesFromLazyOption()
664664

665665
/**
666666
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
667-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "DateTime[]".
667+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]".
668668
*/
669669
public function testResolveFailsIfInvalidTypedArray()
670670
{
@@ -688,7 +688,7 @@ public function testResolveFailsWithNonArray()
688688

689689
/**
690690
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
691-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "integer|stdClass|array|DateTime[]".
691+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]".
692692
*/
693693
public function testResolveFailsIfTypedArrayContainsInvalidTypes()
694694
{
@@ -705,7 +705,7 @@ public function testResolveFailsIfTypedArrayContainsInvalidTypes()
705705

706706
/**
707707
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
708-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but is of type "double[][]".
708+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]".
709709
*/
710710
public function testResolveFailsWithCorrectLevelsButWrongScalar()
711711
{
@@ -1767,4 +1767,151 @@ public function testCountFailsOutsideResolve()
17671767

17681768
count($this->resolver);
17691769
}
1770+
1771+
public function testNestedArrays()
1772+
{
1773+
$this->resolver->setDefined('foo');
1774+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1775+
1776+
$this->assertEquals(array(
1777+
'foo' => array(
1778+
array(
1779+
1, 2,
1780+
),
1781+
),
1782+
), $this->resolver->resolve(
1783+
array(
1784+
'foo' => array(
1785+
array(1, 2),
1786+
),
1787+
)
1788+
));
1789+
}
1790+
1791+
public function testNested2Arrays()
1792+
{
1793+
$this->resolver->setDefined('foo');
1794+
$this->resolver->setAllowedTypes('foo', 'int[][][][]');
1795+
1796+
$this->assertEquals(array(
1797+
'foo' => array(
1798+
array(
1799+
array(
1800+
array(
1801+
1, 2,
1802+
),
1803+
),
1804+
),
1805+
),
1806+
), $this->resolver->resolve(
1807+
array(
1808+
'foo' => array(
1809+
array(
1810+
array(
1811+
array(1, 2),
1812+
),
1813+
),
1814+
),
1815+
)
1816+
));
1817+
}
1818+
1819+
/**
1820+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1821+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]".
1822+
*/
1823+
public function testNestedArraysException()
1824+
{
1825+
$this->resolver->setDefined('foo');
1826+
$this->resolver->setAllowedTypes('foo', 'float[][][][]');
1827+
1828+
$this->resolver->resolve(
1829+
array(
1830+
'foo' => array(
1831+
array(
1832+
array(
1833+
array(1, 2),
1834+
),
1835+
),
1836+
),
1837+
)
1838+
);
1839+
}
1840+
1841+
/**
1842+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1843+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
1844+
*/
1845+
public function testNestedArrayException1()
1846+
{
1847+
$this->resolver->setDefined('foo');
1848+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1849+
$this->resolver->resolve(array(
1850+
'foo' => array(
1851+
array(1, true, 'str', array(2, 3)),
1852+
),
1853+
));
1854+
}
1855+
1856+
/**
1857+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1858+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
1859+
*/
1860+
public function testNestedArrayException2()
1861+
{
1862+
$this->resolver->setDefined('foo');
1863+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1864+
$this->resolver->resolve(array(
1865+
'foo' => array(
1866+
array(true, 'str', array(2, 3)),
1867+
),
1868+
));
1869+
}
1870+
1871+
/**
1872+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1873+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]".
1874+
*/
1875+
public function testNestedArrayException3()
1876+
{
1877+
$this->resolver->setDefined('foo');
1878+
$this->resolver->setAllowedTypes('foo', 'string[][][]');
1879+
$this->resolver->resolve(array(
1880+
'foo' => array(
1881+
array('str', array(1, 2)),
1882+
),
1883+
));
1884+
}
1885+
1886+
/**
1887+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1888+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]".
1889+
*/
1890+
public function testNestedArrayException4()
1891+
{
1892+
$this->resolver->setDefined('foo');
1893+
$this->resolver->setAllowedTypes('foo', 'string[][][]');
1894+
$this->resolver->resolve(array(
1895+
'foo' => array(
1896+
array(
1897+
array('str'), array(1, 2), ),
1898+
),
1899+
));
1900+
}
1901+
1902+
/**
1903+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1904+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]".
1905+
*/
1906+
public function testNestedArrayException5()
1907+
{
1908+
$this->resolver->setDefined('foo');
1909+
$this->resolver->setAllowedTypes('foo', 'string[]');
1910+
$this->resolver->resolve(array(
1911+
'foo' => array(
1912+
array(
1913+
array('str'), array(1, 2), ),
1914+
),
1915+
));
1916+
}
17701917
}

0 commit comments

Comments
 (0)