[OptionResolver] resolve arrays
This commit is contained in:
parent
98934e4c77
commit
6d4812e995
@ -433,7 +433,7 @@ class OptionsResolver implements Options
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->allowedValues[$option] = is_array($allowedValues) ? $allowedValues : array($allowedValues);
|
$this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : array($allowedValues);
|
||||||
|
|
||||||
// Make sure the option is processed
|
// Make sure the option is processed
|
||||||
unset($this->resolved[$option]);
|
unset($this->resolved[$option]);
|
||||||
@ -785,14 +785,13 @@ class OptionsResolver implements Options
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$valid) {
|
if (!$valid) {
|
||||||
throw new InvalidOptionsException(sprintf(
|
$keys = array_keys($invalidTypes);
|
||||||
'The option "%s" with value %s is expected to be of type '.
|
|
||||||
'"%s", but is of type "%s".',
|
if (1 === \count($keys) && '[]' === substr($keys[0], -2)) {
|
||||||
$option,
|
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]));
|
||||||
$this->formatValue($value),
|
}
|
||||||
implode('" or "', $this->allowedTypes[$option]),
|
|
||||||
implode('|', array_keys($invalidTypes))
|
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))));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -877,23 +876,8 @@ class OptionsResolver implements Options
|
|||||||
*/
|
*/
|
||||||
private function verifyTypes($type, $value, array &$invalidTypes)
|
private function verifyTypes($type, $value, array &$invalidTypes)
|
||||||
{
|
{
|
||||||
if ('[]' === substr($type, -2) && is_array($value)) {
|
if (\is_array($value) && '[]' === substr($type, -2)) {
|
||||||
$originalType = $type;
|
return $this->verifyArrayType($type, $value, $invalidTypes);
|
||||||
$type = substr($type, 0, -2);
|
|
||||||
$invalidValues = array_filter( // Filter out valid values, keeping invalid values in the resulting array
|
|
||||||
$value,
|
|
||||||
function ($value) use ($type) {
|
|
||||||
return !self::isValueValidType($type, $value);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$invalidValues) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$invalidTypes[$this->formatTypeOf($value, $originalType)] = true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self::isValueValidType($type, $value)) {
|
if (self::isValueValidType($type, $value)) {
|
||||||
@ -907,6 +891,46 @@ class OptionsResolver implements Options
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function verifyArrayType($type, array $value, array &$invalidTypes, $level = 0)
|
||||||
|
{
|
||||||
|
$type = substr($type, 0, -2);
|
||||||
|
|
||||||
|
$suffix = '[]';
|
||||||
|
while (\strlen($suffix) <= $level * 2) {
|
||||||
|
$suffix .= '[]';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('[]' === substr($type, -2)) {
|
||||||
|
$success = true;
|
||||||
|
foreach ($value as $item) {
|
||||||
|
if (!\is_array($item)) {
|
||||||
|
$invalidTypes[$this->formatTypeOf($item, null).$suffix] = true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) {
|
||||||
|
$success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($value as $item) {
|
||||||
|
if (!self::isValueValidType($type, $item)) {
|
||||||
|
$invalidTypes[$this->formatTypeOf($item, $type).$suffix] = $value;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a resolved option with the given name exists.
|
* Returns whether a resolved option with the given name exists.
|
||||||
*
|
*
|
||||||
@ -990,13 +1014,13 @@ class OptionsResolver implements Options
|
|||||||
while ('[]' === substr($type, -2)) {
|
while ('[]' === substr($type, -2)) {
|
||||||
$type = substr($type, 0, -2);
|
$type = substr($type, 0, -2);
|
||||||
$value = array_shift($value);
|
$value = array_shift($value);
|
||||||
if (!is_array($value)) {
|
if (!\is_array($value)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$suffix .= '[]';
|
$suffix .= '[]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($value)) {
|
if (\is_array($value)) {
|
||||||
$subTypes = array();
|
$subTypes = array();
|
||||||
foreach ($value as $val) {
|
foreach ($value as $val) {
|
||||||
$subTypes[$this->formatTypeOf($val, null)] = true;
|
$subTypes[$this->formatTypeOf($val, null)] = true;
|
||||||
@ -1006,7 +1030,7 @@ class OptionsResolver implements Options
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (is_object($value) ? get_class($value) : gettype($value)).$suffix;
|
return (\is_object($value) ? get_class($value) : gettype($value)).$suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1022,19 +1046,19 @@ class OptionsResolver implements Options
|
|||||||
*/
|
*/
|
||||||
private function formatValue($value)
|
private function formatValue($value)
|
||||||
{
|
{
|
||||||
if (is_object($value)) {
|
if (\is_object($value)) {
|
||||||
return get_class($value);
|
return get_class($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($value)) {
|
if (\is_array($value)) {
|
||||||
return 'array';
|
return 'array';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($value)) {
|
if (\is_string($value)) {
|
||||||
return '"'.$value.'"';
|
return '"'.$value.'"';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_resource($value)) {
|
if (\is_resource($value)) {
|
||||||
return 'resource';
|
return 'resource';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1078,4 +1102,20 @@ class OptionsResolver implements Options
|
|||||||
{
|
{
|
||||||
return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type;
|
return (function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getInvalidValues(array $arrayValues, $type)
|
||||||
|
{
|
||||||
|
$invalidValues = array();
|
||||||
|
|
||||||
|
foreach ($arrayValues as $key => $value) {
|
||||||
|
if (!self::isValueValidType($type, $value)) {
|
||||||
|
$invalidValues[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $invalidValues;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,7 +511,7 @@ class OptionsResolverTest extends TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "DateTime[]".
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "DateTime[]".
|
||||||
*/
|
*/
|
||||||
public function testResolveFailsIfInvalidTypedArray()
|
public function testResolveFailsIfInvalidTypedArray()
|
||||||
{
|
{
|
||||||
@ -535,7 +535,7 @@ class OptionsResolverTest extends TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but is of type "integer|stdClass|array|DateTime[]".
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[]", but one of the elements is of type "stdClass[]".
|
||||||
*/
|
*/
|
||||||
public function testResolveFailsIfTypedArrayContainsInvalidTypes()
|
public function testResolveFailsIfTypedArrayContainsInvalidTypes()
|
||||||
{
|
{
|
||||||
@ -552,7 +552,7 @@ class OptionsResolverTest extends TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but is of type "double[][]".
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "double[][]".
|
||||||
*/
|
*/
|
||||||
public function testResolveFailsWithCorrectLevelsButWrongScalar()
|
public function testResolveFailsWithCorrectLevelsButWrongScalar()
|
||||||
{
|
{
|
||||||
@ -1650,4 +1650,151 @@ class OptionsResolverTest extends TestCase
|
|||||||
|
|
||||||
count($this->resolver);
|
count($this->resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNestedArrays()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'int[][]');
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
1, 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), $this->resolver->resolve(
|
||||||
|
array(
|
||||||
|
'foo' => array(
|
||||||
|
array(1, 2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNested2Arrays()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'int[][][][]');
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
1, 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), $this->resolver->resolve(
|
||||||
|
array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
array(1, 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "float[][][][]", but one of the elements is of type "integer[][][][]".
|
||||||
|
*/
|
||||||
|
public function testNestedArraysException()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'float[][][][]');
|
||||||
|
|
||||||
|
$this->resolver->resolve(
|
||||||
|
array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
array(1, 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
|
||||||
|
*/
|
||||||
|
public function testNestedArrayException1()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'int[][]');
|
||||||
|
$this->resolver->resolve(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(1, true, 'str', array(2, 3)),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "int[][]", but one of the elements is of type "boolean[][]".
|
||||||
|
*/
|
||||||
|
public function testNestedArrayException2()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'int[][]');
|
||||||
|
$this->resolver->resolve(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(true, 'str', array(2, 3)),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "string[][]".
|
||||||
|
*/
|
||||||
|
public function testNestedArrayException3()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'string[][][]');
|
||||||
|
$this->resolver->resolve(array(
|
||||||
|
'foo' => array(
|
||||||
|
array('str', array(1, 2)),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[][][]", but one of the elements is of type "integer[][][]".
|
||||||
|
*/
|
||||||
|
public function testNestedArrayException4()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'string[][][]');
|
||||||
|
$this->resolver->resolve(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
array('str'), array(1, 2), ),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||||
|
* @expectedExceptionMessage The option "foo" with value array is expected to be of type "string[]", but one of the elements is of type "array[]".
|
||||||
|
*/
|
||||||
|
public function testNestedArrayException5()
|
||||||
|
{
|
||||||
|
$this->resolver->setDefined('foo');
|
||||||
|
$this->resolver->setAllowedTypes('foo', 'string[]');
|
||||||
|
$this->resolver->resolve(array(
|
||||||
|
'foo' => array(
|
||||||
|
array(
|
||||||
|
array('str'), array(1, 2), ),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user