Support array of types in allowed type
This commit is contained in:
parent
0f5e38c732
commit
d066a23860
@ -5,6 +5,7 @@ CHANGELOG
|
||||
-----
|
||||
|
||||
* added `OptionsResolverIntrospector` to inspect options definitions inside an `OptionsResolver` instance
|
||||
* added array of types support in allowed types (e.g int[])
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
@ -792,21 +792,12 @@ class OptionsResolver implements Options
|
||||
// Validate the type of the resolved option
|
||||
if (isset($this->allowedTypes[$option])) {
|
||||
$valid = false;
|
||||
$invalidTypes = array();
|
||||
|
||||
foreach ($this->allowedTypes[$option] as $type) {
|
||||
$type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type;
|
||||
|
||||
if (function_exists($isFunction = 'is_'.$type)) {
|
||||
if ($isFunction($value)) {
|
||||
$valid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value instanceof $type) {
|
||||
$valid = true;
|
||||
if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -818,7 +809,7 @@ class OptionsResolver implements Options
|
||||
$option,
|
||||
$this->formatValue($value),
|
||||
implode('" or "', $this->allowedTypes[$option]),
|
||||
$this->formatTypeOf($value)
|
||||
implode('|', array_keys($invalidTypes))
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -895,6 +886,45 @@ class OptionsResolver implements Options
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param mixed $value
|
||||
* @param array &$invalidTypes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function verifyTypes($type, $value, array &$invalidTypes)
|
||||
{
|
||||
if ('[]' === substr($type, -2) && is_array($value)) {
|
||||
$originalType = $type;
|
||||
$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 (function_exists($isFunction = 'is_'.$type) && !$isFunction($value)) || !$value instanceof $type;
|
||||
}
|
||||
);
|
||||
|
||||
if (!$invalidValues) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$invalidTypes[$this->formatTypeOf($value, $originalType)] = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$invalidTypes) {
|
||||
$invalidTypes[$this->formatTypeOf($value, null)] = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a resolved option with the given name exists.
|
||||
*
|
||||
@ -964,12 +994,37 @@ class OptionsResolver implements Options
|
||||
* non-technical people.
|
||||
*
|
||||
* @param mixed $value The value to return the type of
|
||||
* @param string $type
|
||||
*
|
||||
* @return string The type of the value
|
||||
*/
|
||||
private function formatTypeOf($value)
|
||||
private function formatTypeOf($value, $type)
|
||||
{
|
||||
return is_object($value) ? get_class($value) : gettype($value);
|
||||
$suffix = '';
|
||||
|
||||
if ('[]' === substr($type, -2)) {
|
||||
$suffix = '[]';
|
||||
$type = substr($type, 0, -2);
|
||||
while ('[]' === substr($type, -2)) {
|
||||
$type = substr($type, 0, -2);
|
||||
$value = array_shift($value);
|
||||
if (!is_array($value)) {
|
||||
break;
|
||||
}
|
||||
$suffix .= '[]';
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$subTypes = array();
|
||||
foreach ($value as $val) {
|
||||
$subTypes[$this->formatTypeOf($val, null)] = true;
|
||||
}
|
||||
|
||||
return implode('|', array_keys($subTypes)).$suffix;
|
||||
}
|
||||
}
|
||||
|
||||
return (is_object($value) ? get_class($value) : gettype($value)).$suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -500,6 +500,65 @@ class OptionsResolverTest extends TestCase
|
||||
$this->resolver->resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[]".
|
||||
*/
|
||||
public function testResolveFailsIfInvalidTypedArray()
|
||||
{
|
||||
$this->resolver->setDefined('foo');
|
||||
$this->resolver->setAllowedTypes('foo', 'int[]');
|
||||
|
||||
$this->resolver->resolve(array('foo' => array(new \DateTime())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||
* @expectedExceptionMessage The option "foo" with value "bar" is expected to be of type "int[]", but is of type "string".
|
||||
*/
|
||||
public function testResolveFailsWithNonArray()
|
||||
{
|
||||
$this->resolver->setDefined('foo');
|
||||
$this->resolver->setAllowedTypes('foo', 'int[]');
|
||||
|
||||
$this->resolver->resolve(array('foo' => 'bar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[]".
|
||||
*/
|
||||
public function testResolveFailsIfTypedArrayContainsInvalidTypes()
|
||||
{
|
||||
$this->resolver->setDefined('foo');
|
||||
$this->resolver->setAllowedTypes('foo', 'int[]');
|
||||
$values = range(1, 5);
|
||||
$values[] = new \stdClass();
|
||||
$values[] = array();
|
||||
$values[] = new \DateTime();
|
||||
$values[] = 123;
|
||||
|
||||
$this->resolver->resolve(array('foo' => $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[][]".
|
||||
*/
|
||||
public function testResolveFailsWithCorrectLevelsButWrongScalar()
|
||||
{
|
||||
$this->resolver->setDefined('foo');
|
||||
$this->resolver->setAllowedTypes('foo', 'int[][]');
|
||||
|
||||
$this->resolver->resolve(
|
||||
array(
|
||||
'foo' => array(
|
||||
array(1.2),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidTypes
|
||||
*/
|
||||
@ -568,6 +627,32 @@ class OptionsResolverTest extends TestCase
|
||||
$this->assertNotEmpty($this->resolver->resolve());
|
||||
}
|
||||
|
||||
public function testResolveSucceedsIfTypedArray()
|
||||
{
|
||||
$this->resolver->setDefault('foo', null);
|
||||
$this->resolver->setAllowedTypes('foo', array('null', 'DateTime[]'));
|
||||
|
||||
$data = array(
|
||||
'foo' => array(
|
||||
new \DateTime(),
|
||||
new \DateTime(),
|
||||
),
|
||||
);
|
||||
$result = $this->resolver->resolve($data);
|
||||
$this->assertEquals($data, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||
*/
|
||||
public function testResolveFailsIfNotInstanceOfClass()
|
||||
{
|
||||
$this->resolver->setDefault('foo', 'bar');
|
||||
$this->resolver->setAllowedTypes('foo', '\stdClass');
|
||||
|
||||
$this->resolver->resolve();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// addAllowedTypes()
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
Reference in New Issue
Block a user