Skip to content

Commit 9f12ed6

Browse files
Merge branch '3.4' into 4.0
* 3.4: [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 [HttpFoundation] don't encode cookie name for BC improve deprecation messages minor #27858 [Console] changed warning verbosity; fixes typo (adrian-enspired) AppBundle->App. [DI] Fix dumping ignore-on-uninitialized references to synthetic services
2 parents ea9aacb + 638f5ad commit 9f12ed6

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
@@ -433,7 +433,7 @@ public function setAllowedValues($option, $allowedValues)
433433
));
434434
}
435435

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

438438
// Make sure the option is processed
439439
unset($this->resolved[$option]);
@@ -785,14 +785,13 @@ public function offsetGet($option)
785785
}
786786

787787
if (!$valid) {
788-
throw new InvalidOptionsException(sprintf(
789-
'The option "%s" with value %s is expected to be of type '.
790-
'"%s", but is of type "%s".',
791-
$option,
792-
$this->formatValue($value),
793-
implode('" or "', $this->allowedTypes[$option]),
794-
implode('|', array_keys($invalidTypes))
795-
));
788+
$keys = array_keys($invalidTypes);
789+
790+
if (1 === \count($keys) && '[]' === substr($keys[0], -2)) {
791+
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]));
792+
}
793+
794+
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))));
796795
}
797796
}
798797

@@ -868,32 +867,10 @@ public function offsetGet($option)
868867
return $value;
869868
}
870869

871-
/**
872-
* @param string $type
873-
* @param mixed $value
874-
* @param array &$invalidTypes
875-
*
876-
* @return bool
877-
*/
878-
private function verifyTypes($type, $value, array &$invalidTypes)
870+
private function verifyTypes(string $type, $value, array &$invalidTypes): bool
879871
{
880-
if ('[]' === substr($type, -2) && is_array($value)) {
881-
$originalType = $type;
882-
$type = substr($type, 0, -2);
883-
$invalidValues = array_filter( // Filter out valid values, keeping invalid values in the resulting array
884-
$value,
885-
function ($value) use ($type) {
886-
return !self::isValueValidType($type, $value);
887-
}
888-
);
889-
890-
if (!$invalidValues) {
891-
return true;
892-
}
893-
894-
$invalidTypes[$this->formatTypeOf($value, $originalType)] = true;
895-
896-
return false;
872+
if (\is_array($value) && '[]' === substr($type, -2)) {
873+
return $this->verifyArrayType($type, $value, $invalidTypes);
897874
}
898875

899876
if (self::isValueValidType($type, $value)) {
@@ -907,6 +884,43 @@ function ($value) use ($type) {
907884
return false;
908885
}
909886

887+
private function verifyArrayType(string $type, array $value, array &$invalidTypes, int $level = 0): bool
888+
{
889+
$type = substr($type, 0, -2);
890+
891+
$suffix = '[]';
892+
while (\strlen($suffix) <= $level * 2) {
893+
$suffix .= '[]';
894+
}
895+
896+
if ('[]' === substr($type, -2)) {
897+
$success = true;
898+
foreach ($value as $item) {
899+
if (!\is_array($item)) {
900+
$invalidTypes[$this->formatTypeOf($item, null).$suffix] = true;
901+
902+
return false;
903+
}
904+
905+
if (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
906+
$success = false;
907+
}
908+
}
909+
910+
return $success;
911+
}
912+
913+
foreach ($value as $item) {
914+
if (!self::isValueValidType($type, $item)) {
915+
$invalidTypes[$this->formatTypeOf($item, $type).$suffix] = $value;
916+
917+
return false;
918+
}
919+
}
920+
921+
return true;
922+
}
923+
910924
/**
911925
* Returns whether a resolved option with the given name exists.
912926
*
@@ -987,13 +1001,13 @@ private function formatTypeOf($value, ?string $type): string
9871001
while ('[]' === substr($type, -2)) {
9881002
$type = substr($type, 0, -2);
9891003
$value = array_shift($value);
990-
if (!is_array($value)) {
1004+
if (!\is_array($value)) {
9911005
break;
9921006
}
9931007
$suffix .= '[]';
9941008
}
9951009

996-
if (is_array($value)) {
1010+
if (\is_array($value)) {
9971011
$subTypes = array();
9981012
foreach ($value as $val) {
9991013
$subTypes[$this->formatTypeOf($val, null)] = true;
@@ -1003,7 +1017,7 @@ private function formatTypeOf($value, ?string $type): string
10031017
}
10041018
}
10051019

1006-
return (is_object($value) ? get_class($value) : gettype($value)).$suffix;
1020+
return (\is_object($value) ? get_class($value) : gettype($value)).$suffix;
10071021
}
10081022

10091023
/**
@@ -1017,19 +1031,19 @@ private function formatTypeOf($value, ?string $type): string
10171031
*/
10181032
private function formatValue($value): string
10191033
{
1020-
if (is_object($value)) {
1034+
if (\is_object($value)) {
10211035
return get_class($value);
10221036
}
10231037

1024-
if (is_array($value)) {
1038+
if (\is_array($value)) {
10251039
return 'array';
10261040
}
10271041

1028-
if (is_string($value)) {
1042+
if (\is_string($value)) {
10291043
return '"'.$value.'"';
10301044
}
10311045

1032-
if (is_resource($value)) {
1046+
if (\is_resource($value)) {
10331047
return 'resource';
10341048
}
10351049

@@ -1065,8 +1079,21 @@ private function formatValues(array $values): string
10651079
return implode(', ', $values);
10661080
}
10671081

1068-
private static function isValueValidType($type, $value)
1082+
private static function isValueValidType(string $type, $value): bool
10691083
{
10701084
return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type;
10711085
}
1086+
1087+
private function getInvalidValues(array $arrayValues, string $type): array
1088+
{
1089+
$invalidValues = array();
1090+
1091+
foreach ($arrayValues as $key => $value) {
1092+
if (!self::isValueValidType($type, $value)) {
1093+
$invalidValues[$key] = $value;
1094+
}
1095+
}
1096+
1097+
return $invalidValues;
1098+
}
10721099
}

Tests/OptionsResolverTest.php

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

484484
/**
485485
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
486-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "DateTime[]".
486+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]".
487487
*/
488488
public function testResolveFailsIfInvalidTypedArray()
489489
{
@@ -507,7 +507,7 @@ public function testResolveFailsWithNonArray()
507507

508508
/**
509509
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
510-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "integer|stdClass|array|DateTime[]".
510+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]".
511511
*/
512512
public function testResolveFailsIfTypedArrayContainsInvalidTypes()
513513
{
@@ -524,7 +524,7 @@ public function testResolveFailsIfTypedArrayContainsInvalidTypes()
524524

525525
/**
526526
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
527-
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but is of type "double[][]".
527+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]".
528528
*/
529529
public function testResolveFailsWithCorrectLevelsButWrongScalar()
530530
{
@@ -1586,4 +1586,151 @@ public function testCountFailsOutsideResolve()
15861586

15871587
count($this->resolver);
15881588
}
1589+
1590+
public function testNestedArrays()
1591+
{
1592+
$this->resolver->setDefined('foo');
1593+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1594+
1595+
$this->assertEquals(array(
1596+
'foo' => array(
1597+
array(
1598+
1, 2,
1599+
),
1600+
),
1601+
), $this->resolver->resolve(
1602+
array(
1603+
'foo' => array(
1604+
array(1, 2),
1605+
),
1606+
)
1607+
));
1608+
}
1609+
1610+
public function testNested2Arrays()
1611+
{
1612+
$this->resolver->setDefined('foo');
1613+
$this->resolver->setAllowedTypes('foo', 'int[][][][]');
1614+
1615+
$this->assertEquals(array(
1616+
'foo' => array(
1617+
array(
1618+
array(
1619+
array(
1620+
1, 2,
1621+
),
1622+
),
1623+
),
1624+
),
1625+
), $this->resolver->resolve(
1626+
array(
1627+
'foo' => array(
1628+
array(
1629+
array(
1630+
array(1, 2),
1631+
),
1632+
),
1633+
),
1634+
)
1635+
));
1636+
}
1637+
1638+
/**
1639+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1640+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]".
1641+
*/
1642+
public function testNestedArraysException()
1643+
{
1644+
$this->resolver->setDefined('foo');
1645+
$this->resolver->setAllowedTypes('foo', 'float[][][][]');
1646+
1647+
$this->resolver->resolve(
1648+
array(
1649+
'foo' => array(
1650+
array(
1651+
array(
1652+
array(1, 2),
1653+
),
1654+
),
1655+
),
1656+
)
1657+
);
1658+
}
1659+
1660+
/**
1661+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1662+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
1663+
*/
1664+
public function testNestedArrayException1()
1665+
{
1666+
$this->resolver->setDefined('foo');
1667+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1668+
$this->resolver->resolve(array(
1669+
'foo' => array(
1670+
array(1, true, 'str', array(2, 3)),
1671+
),
1672+
));
1673+
}
1674+
1675+
/**
1676+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1677+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
1678+
*/
1679+
public function testNestedArrayException2()
1680+
{
1681+
$this->resolver->setDefined('foo');
1682+
$this->resolver->setAllowedTypes('foo', 'int[][]');
1683+
$this->resolver->resolve(array(
1684+
'foo' => array(
1685+
array(true, 'str', array(2, 3)),
1686+
),
1687+
));
1688+
}
1689+
1690+
/**
1691+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1692+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]".
1693+
*/
1694+
public function testNestedArrayException3()
1695+
{
1696+
$this->resolver->setDefined('foo');
1697+
$this->resolver->setAllowedTypes('foo', 'string[][][]');
1698+
$this->resolver->resolve(array(
1699+
'foo' => array(
1700+
array('str', array(1, 2)),
1701+
),
1702+
));
1703+
}
1704+
1705+
/**
1706+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1707+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]".
1708+
*/
1709+
public function testNestedArrayException4()
1710+
{
1711+
$this->resolver->setDefined('foo');
1712+
$this->resolver->setAllowedTypes('foo', 'string[][][]');
1713+
$this->resolver->resolve(array(
1714+
'foo' => array(
1715+
array(
1716+
array('str'), array(1, 2), ),
1717+
),
1718+
));
1719+
}
1720+
1721+
/**
1722+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
1723+
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]".
1724+
*/
1725+
public function testNestedArrayException5()
1726+
{
1727+
$this->resolver->setDefined('foo');
1728+
$this->resolver->setAllowedTypes('foo', 'string[]');
1729+
$this->resolver->resolve(array(
1730+
'foo' => array(
1731+
array(
1732+
array('str'), array(1, 2), ),
1733+
),
1734+
));
1735+
}
15891736
}

0 commit comments

Comments
 (0)