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 `OptionsResolverIntrospector` to inspect options definitions inside an `OptionsResolver` instance
|
||||||
|
* added array of types support in allowed types (e.g int[])
|
||||||
|
|
||||||
2.6.0
|
2.6.0
|
||||||
-----
|
-----
|
||||||
|
@ -792,21 +792,12 @@ class OptionsResolver implements Options
|
|||||||
// Validate the type of the resolved option
|
// Validate the type of the resolved option
|
||||||
if (isset($this->allowedTypes[$option])) {
|
if (isset($this->allowedTypes[$option])) {
|
||||||
$valid = false;
|
$valid = false;
|
||||||
|
$invalidTypes = array();
|
||||||
|
|
||||||
foreach ($this->allowedTypes[$option] as $type) {
|
foreach ($this->allowedTypes[$option] as $type) {
|
||||||
$type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type;
|
$type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type;
|
||||||
|
|
||||||
if (function_exists($isFunction = 'is_'.$type)) {
|
if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
|
||||||
if ($isFunction($value)) {
|
|
||||||
$valid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($value instanceof $type) {
|
|
||||||
$valid = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -818,7 +809,7 @@ class OptionsResolver implements Options
|
|||||||
$option,
|
$option,
|
||||||
$this->formatValue($value),
|
$this->formatValue($value),
|
||||||
implode('" or "', $this->allowedTypes[$option]),
|
implode('" or "', $this->allowedTypes[$option]),
|
||||||
$this->formatTypeOf($value)
|
implode('|', array_keys($invalidTypes))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -895,6 +886,45 @@ class OptionsResolver implements Options
|
|||||||
return $value;
|
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.
|
* Returns whether a resolved option with the given name exists.
|
||||||
*
|
*
|
||||||
@ -963,13 +993,38 @@ class OptionsResolver implements Options
|
|||||||
* parameters should usually not be included in messages aimed at
|
* parameters should usually not be included in messages aimed at
|
||||||
* non-technical people.
|
* non-technical people.
|
||||||
*
|
*
|
||||||
* @param mixed $value The value to return the type of
|
* @param mixed $value The value to return the type of
|
||||||
|
* @param string $type
|
||||||
*
|
*
|
||||||
* @return string The type of the value
|
* @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();
|
$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
|
* @dataProvider provideInvalidTypes
|
||||||
*/
|
*/
|
||||||
@ -568,6 +627,32 @@ class OptionsResolverTest extends TestCase
|
|||||||
$this->assertNotEmpty($this->resolver->resolve());
|
$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()
|
// addAllowedTypes()
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
Reference in New Issue
Block a user