[OptionsResolver] Added option type validation capabilities

This commit is contained in:
Bernhard Schussek 2012-05-23 16:46:54 +02:00
parent 0af5f06703
commit 97de0041a1
3 changed files with 256 additions and 3 deletions

View File

@ -120,10 +120,10 @@ class Options implements \ArrayAccess, \Iterator, \Countable
* Passed closures should have the following signature:
*
* <code>
* function (Options $options, $previousValue)
* function (Options $options, $value)
* </code>
*
* The second parameter passed to the closure is the previous default
* The second parameter passed to the closure is the current default
* value of the option.
*
* @param string $option The option name.

View File

@ -47,6 +47,12 @@ class OptionsResolver
*/
private $allowedValues = array();
/**
* A list of accepted types for each option.
* @var array
*/
private $allowedTypes = array();
/**
* A list of filters transforming each resolved options.
* @var array
@ -222,6 +228,48 @@ class OptionsResolver
return $this;
}
/**
* Sets allowed types for a list of options.
*
* @param array $allowedTypes A list of option names as keys and type
* names passed as string or array as values.
*
* @return OptionsResolver The resolver instance.
*
* @throws InvalidOptionsException If an option has not been defined for
* which an allowed type is set.
*/
public function setAllowedTypes(array $allowedTypes)
{
$this->validateOptionNames(array_keys($allowedTypes));
$this->allowedTypes = array_replace($this->allowedTypes, $allowedTypes);
return $this;
}
/**
* Adds allowed types for a list of options.
*
* The types are merged with the allowed types defined previously.
*
* @param array $allowedTypes A list of option names as keys and type
* names passed as string or array as values.
*
* @return OptionsResolver The resolver instance.
*
* @throws InvalidOptionsException If an option has not been defined for
* which an allowed type is set.
*/
public function addAllowedTypes(array $allowedTypes)
{
$this->validateOptionNames(array_keys($allowedTypes));
$this->allowedTypes = array_merge_recursive($this->allowedTypes, $allowedTypes);
return $this;
}
/**
* Sets filters that are applied on resolved options.
*
@ -312,8 +360,8 @@ class OptionsResolver
// Resolve options
$resolvedOptions = $combinedOptions->all();
// Validate against allowed values
$this->validateOptionValues($resolvedOptions);
$this->validateOptionTypes($resolvedOptions);
return $resolvedOptions;
}
@ -381,4 +429,44 @@ class OptionsResolver
}
}
}
/**
* Validates that the given options match the allowed types and
* throws an exception otherwise.
*
* @param array $options A list of options.
*
* @throws InvalidOptionsException If any of the types does not match the
* allowed types of the option.
*/
private function validateOptionTypes(array $options)
{
foreach ($this->allowedTypes as $option => $allowedTypes) {
$value = $options[$option];
$allowedTypes = (array) $allowedTypes;
foreach ($allowedTypes as $type) {
$isFunction = 'is_' . $type;
if (function_exists($isFunction) && $isFunction($value)) {
continue 2;
} elseif ($value instanceof $type) {
continue 2;
}
}
$printableValue = is_object($value)
? get_class($value)
: (is_array($value)
? 'Array'
: (string) $value);
throw new InvalidOptionsException(sprintf(
'The option "%s" with value "%s" is expected to be of type "%s"',
$option,
$printableValue,
implode('", "', $allowedTypes)
));
}
}
}

View File

@ -276,6 +276,171 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase
));
}
public function testResolveSucceedsIfOptionTypeAllowed()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => 'string',
));
$options = array(
'one' => 'one',
);
$this->assertEquals(array(
'one' => 'one',
), $this->resolver->resolve($options));
}
public function testResolveSucceedsIfOptionTypeAllowedPassArray()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => array('string', 'bool'),
));
$options = array(
'one' => true,
);
$this->assertEquals(array(
'one' => true,
), $this->resolver->resolve($options));
}
public function testResolveSucceedsIfOptionTypeAllowedPassObject()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => 'object',
));
$object = new \stdClass();
$options = array(
'one' => $object,
);
$this->assertEquals(array(
'one' => $object,
), $this->resolver->resolve($options));
}
public function testResolveSucceedsIfOptionTypeAllowedPassClass()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => '\stdClass',
));
$object = new \stdClass();
$options = array(
'one' => $object,
);
$this->assertEquals(array(
'one' => $object,
), $this->resolver->resolve($options));
}
public function testResolveSucceedsIfOptionTypeAllowedAddTypes()
{
$this->resolver->setDefaults(array(
'one' => '1',
'two' => '2',
));
$this->resolver->setAllowedTypes(array(
'one' => 'string',
'two' => 'bool',
));
$this->resolver->addAllowedTypes(array(
'one' => 'float',
'two' => 'integer',
));
$options = array(
'one' => 1.23,
'two' => false,
);
$this->assertEquals(array(
'one' => 1.23,
'two' => false,
), $this->resolver->resolve($options));
}
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function testResolveFailsIfOptionTypeNotAllowed()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => array('string', 'bool'),
));
$this->resolver->resolve(array(
'one' => 1.23,
));
}
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function testResolveFailsIfOptionTypeNotAllowedMultipleOptions()
{
$this->resolver->setDefaults(array(
'one' => '1',
'two' => '2',
));
$this->resolver->setAllowedTypes(array(
'one' => 'string',
'two' => 'bool',
));
$this->resolver->resolve(array(
'one' => 'foo',
'two' => 1.23,
));
}
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function testResolveFailsIfOptionTypeNotAllowedAddTypes()
{
$this->resolver->setDefaults(array(
'one' => '1',
));
$this->resolver->setAllowedTypes(array(
'one' => 'string',
));
$this->resolver->addAllowedTypes(array(
'one' => 'bool',
));
$this->resolver->resolve(array(
'one' => 1.23,
));
}
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
*/