2012-04-17 17:14:09 +01:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2012-05-10 15:37:03 +01:00
namespace Symfony\Component\OptionsResolver ;
2012-04-17 17:14:09 +01:00
2014-10-06 16:43:30 +01:00
use Symfony\Component\OptionsResolver\Exception\AccessException ;
2014-10-21 10:07:51 +01:00
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException ;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException ;
2014-10-22 20:14:29 +01:00
use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException ;
2014-10-06 16:43:30 +01:00
use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException ;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException ;
2014-10-21 10:07:51 +01:00
2012-04-17 17:14:09 +01:00
/**
2014-10-06 16:43:30 +01:00
* Validates options and merges them with default values .
2012-04-17 17:14:09 +01:00
*
* @ author Bernhard Schussek < bschussek @ gmail . com >
2012-05-24 04:31:42 +01:00
* @ author Tobias Schultze < http :// tobion . de >
2012-04-17 17:14:09 +01:00
*/
2014-10-06 16:43:30 +01:00
class OptionsResolver implements Options , OptionsResolverInterface
2012-04-17 17:14:09 +01:00
{
2014-10-06 16:43:30 +01:00
/**
* The fully qualified name of the { @ link Options } interface .
*
* @ internal
*/
const OPTIONS_INTERFACE = 'Symfony\\Component\\OptionsResolver\\Options' ;
/**
* The names of all defined options .
*
* @ var array
*/
private $defined = array ();
2014-10-21 10:07:51 +01:00
/**
* The default option values .
2014-10-06 16:43:30 +01:00
*
* @ var array
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
private $defaults = array ();
2014-10-21 10:07:51 +01:00
/**
2014-10-06 16:43:30 +01:00
* The names of required options .
*
2014-10-21 10:07:51 +01:00
* @ var array
*/
2014-10-06 16:43:30 +01:00
private $required = array ();
2014-10-21 10:07:51 +01:00
/**
2014-10-06 16:43:30 +01:00
* The resolved option values .
*
2014-10-21 10:07:51 +01:00
* @ var array
*/
2014-10-06 16:43:30 +01:00
private $resolved = array ();
/**
* A list of normalizer closures .
*
* @ var \Closure []
*/
private $normalizers = array ();
2014-10-21 10:07:51 +01:00
/**
* A list of accepted values for each option .
2014-10-06 16:43:30 +01:00
*
2014-10-21 10:07:51 +01:00
* @ var array
*/
private $allowedValues = array ();
/**
* A list of accepted types for each option .
2014-10-06 16:43:30 +01:00
*
2014-10-21 10:07:51 +01:00
* @ var array
*/
private $allowedTypes = array ();
/**
2014-10-06 16:43:30 +01:00
* A list of closures for evaluating lazy options .
*
* @ var array
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
private $lazy = array ();
2014-10-21 10:07:51 +01:00
/**
2014-10-06 16:43:30 +01:00
* A list of lazy options whose closure is currently being called .
*
* This list helps detecting circular dependencies between lazy options .
*
* @ var array
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
private $calling = array ();
2014-10-21 10:07:51 +01:00
/**
2014-10-06 16:43:30 +01:00
* Whether the instance is locked for reading .
*
* Once locked , the options cannot be changed anymore . This is
* necessary in order to avoid inconsistencies during the resolving
* process . If any option is changed after being read , all evaluated
* lazy options that depend on this option would become invalid .
*
* @ var bool
*/
private $locked = false ;
2014-11-29 09:15:31 +00:00
private static $typeAliases = array (
'boolean' => 'bool' ,
'integer' => 'int' ,
'double' => 'float' ,
);
2014-10-06 16:43:30 +01:00
/**
* Sets the default value of a given option .
*
* If the default value should be set based on other options , you can pass
* a closure with the following signature :
*
* function ( Options $options ) {
* // ...
* }
*
* The closure will be evaluated when { @ link resolve ()} is called . The
* closure has access to the resolved values of other options through the
* passed { @ link Options } instance :
*
* function ( Options $options ) {
* if ( isset ( $options [ 'port' ])) {
* // ...
* }
* }
*
* If you want to access the previously set default value , add a second
* argument to the closure ' s signature :
*
* $options -> setDefault ( 'name' , 'Default Name' );
*
* $options -> setDefault ( 'name' , function ( Options $options , $previousValue ) {
* // 'Default Name' === $previousValue
* });
*
* This is mostly useful if the configuration of the { @ link Options } object
* is spread across different locations of your code , such as base and
* sub - classes .
*
* @ param string $option The name of the option
* @ param mixed $value The default value of the option
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setDefault ( $option , $value )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
// Setting is not possible once resolving starts, because then lazy
// options could manipulate the state of the object, leading to
// inconsistent results.
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Default values cannot be set from a lazy option or normalizer.' );
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
// If an option is a closure that should be evaluated lazily, store it
// in the "lazy" property.
if ( $value instanceof \Closure ) {
$reflClosure = new \ReflectionFunction ( $value );
$params = $reflClosure -> getParameters ();
if ( isset ( $params [ 0 ]) && null !== ( $class = $params [ 0 ] -> getClass ()) && self :: OPTIONS_INTERFACE === $class -> name ) {
// Initialize the option if no previous value exists
if ( ! isset ( $this -> defaults [ $option ])) {
$this -> defaults [ $option ] = null ;
}
// Ignore previous lazy options if the closure has no second parameter
if ( ! isset ( $this -> lazy [ $option ]) || ! isset ( $params [ 1 ])) {
$this -> lazy [ $option ] = array ();
}
// Store closure for later evaluation
$this -> lazy [ $option ][] = $value ;
$this -> defined [ $option ] = true ;
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
return $this ;
}
}
// This option is not lazy anymore
unset ( $this -> lazy [ $option ]);
// Yet undefined options can be marked as resolved, because we only need
// to resolve options with lazy closures, normalizers or validation
// rules, none of which can exist for undefined options
// If the option was resolved before, update the resolved value
if ( ! isset ( $this -> defined [ $option ]) || array_key_exists ( $option , $this -> resolved )) {
$this -> resolved [ $option ] = $value ;
}
$this -> defaults [ $option ] = $value ;
$this -> defined [ $option ] = true ;
2014-10-21 10:07:51 +01:00
return $this ;
}
/**
2014-10-06 16:43:30 +01:00
* Sets a list of default values .
*
* @ param array $defaults The default values to set
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setDefaults ( array $defaults )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
foreach ( $defaults as $option => $value ) {
$this -> setDefault ( $option , $value );
2014-10-21 10:07:51 +01:00
}
return $this ;
}
/**
2014-10-06 16:43:30 +01:00
* Returns whether a default value is set for an option .
*
* Returns true if { @ link setDefault ()} was called for this option .
* An option is also considered set if it was set to null .
*
* @ param string $option The option name
*
* @ return bool Whether a default value is set
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function hasDefault ( $option )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
return array_key_exists ( $option , $this -> defaults );
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Marks one or more options as required .
*
* @ param string | string [] $optionNames One or more option names
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setRequired ( $optionNames )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Options cannot be made required from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
foreach (( array ) $optionNames as $key => $option ) {
$this -> defined [ $option ] = true ;
$this -> required [ $option ] = true ;
2014-10-21 10:07:51 +01:00
}
return $this ;
}
/**
2014-10-06 16:43:30 +01:00
* Returns whether an option is required .
*
* An option is required if it was passed to { @ link setRequired ()} .
*
* @ param string $option The name of the option
*
* @ return bool Whether the option is required
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function isRequired ( $option )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
return isset ( $this -> required [ $option ]);
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Returns the names of all required options .
*
* @ return string [] The names of the required options
*
* @ see isRequired ()
*/
public function getRequiredOptions ()
{
return array_keys ( $this -> required );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Returns whether an option is missing a default value .
*
* An option is missing if it was passed to { @ link setRequired ()}, but not
* to { @ link setDefault ()} . This option must be passed explicitly to
* { @ link resolve ()}, otherwise an exception will be thrown .
*
* @ param string $option The name of the option
*
* @ return bool Whether the option is missing
*/
public function isMissing ( $option )
{
return isset ( $this -> required [ $option ]) && ! array_key_exists ( $option , $this -> defaults );
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Returns the names of all options missing a default value .
*
* @ return string [] The names of the missing options
*
* @ see isMissing ()
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function getMissingOptions ()
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
return array_keys ( array_diff_key ( $this -> required , $this -> defaults ));
}
/**
* Defines a valid option name .
*
* Defines an option name without setting a default value . The option will
* be accepted when passed to { @ link resolve ()} . When not passed , the
* option will not be included in the resolved options .
*
* @ param string | string [] $optionNames One or more option names
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-06 16:43:30 +01:00
*/
public function setDefined ( $optionNames )
{
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Options cannot be defined from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
foreach (( array ) $optionNames as $key => $option ) {
$this -> defined [ $option ] = true ;
}
2014-10-21 10:07:51 +01:00
return $this ;
}
/**
2014-10-06 16:43:30 +01:00
* Returns whether an option is defined .
*
* Returns true for any option passed to { @ link setDefault ()},
* { @ link setRequired ()} or { @ link setDefined ()} .
*
* @ param string $option The option name
*
* @ return bool Whether the option is defined
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function isDefined ( $option )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
return isset ( $this -> defined [ $option ]);
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Returns the names of all defined options .
*
* @ return string [] The names of the defined options
*
* @ see isDefined ()
*/
public function getDefinedOptions ()
{
return array_keys ( $this -> defined );
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Sets the normalizer for an option .
*
* The normalizer should be a closure with the following signature :
*
* `` ` php
* function ( Options $options , $value ) {
* // ...
* }
* `` `
*
* The closure is invoked when { @ link resolve ()} is called . The closure
* has access to the resolved values of other options through the passed
* { @ link Options } instance .
*
* The second parameter passed to the closure is the value of
* the option .
*
* The resolved option value is set to the return value of the closure .
*
* @ param string $option The option name
* @ param \Closure $normalizer The normalizer
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
* @ throws UndefinedOptionsException If the option is undefined
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setNormalizer ( $option , \Closure $normalizer )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Normalizers cannot be set from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
if ( ! isset ( $this -> defined [ $option ])) {
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
));
}
$this -> normalizers [ $option ] = $normalizer ;
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
2014-10-21 10:07:51 +01:00
return $this ;
}
/**
2015-03-01 02:08:17 +00:00
* Sets the normalizers for an array of options .
*
* @ param array $normalizers An array of closures
*
* @ return OptionsResolver This instance
*
* @ throws UndefinedOptionsException If the option is undefined
* @ throws AccessException If called from a lazy option or normalizer
*
* @ see setNormalizer ()
*
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-21 10:07:51 +01:00
*/
public function setNormalizers ( array $normalizers )
{
2015-02-27 16:11:51 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use setNormalizer() instead.' , E_USER_DEPRECATED );
2014-10-21 10:07:51 +01:00
foreach ( $normalizers as $option => $normalizer ) {
2014-10-06 16:43:30 +01:00
$this -> setNormalizer ( $option , $normalizer );
2014-10-21 10:07:51 +01:00
}
return $this ;
}
/**
2014-10-06 16:43:30 +01:00
* Sets allowed values for an option .
*
* Instead of passing values , you may also pass a closures with the
* following signature :
*
* function ( $value ) {
* // return true or false
* }
*
* The closure receives the value as argument and should return true to
* accept the value and false to reject the value .
*
* @ param string $option The option name
* @ param mixed $allowedValues One or more acceptable values / closures
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws UndefinedOptionsException If the option is undefined
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setAllowedValues ( $option , $allowedValues = null )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Allowed values cannot be set from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
// BC
if ( is_array ( $option ) && null === $allowedValues ) {
2015-02-28 03:34:22 +00:00
trigger_error ( 'Calling the ' . __METHOD__ . ' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.' , E_USER_DEPRECATED );
2015-02-27 16:11:51 +00:00
2014-10-06 16:43:30 +01:00
foreach ( $option as $optionName => $optionValues ) {
$this -> setAllowedValues ( $optionName , $optionValues );
}
return $this ;
}
if ( ! isset ( $this -> defined [ $option ])) {
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
));
}
2015-03-01 02:42:24 +00:00
$this -> allowedValues [ $option ] = is_array ( $allowedValues ) ? $allowedValues : array ( $allowedValues );
2014-10-06 16:43:30 +01:00
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
return $this ;
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Adds allowed values for an option .
*
* The values are merged with the allowed values defined previously .
*
* Instead of passing values , you may also pass a closures with the
* following signature :
*
* function ( $value ) {
* // return true or false
* }
*
* The closure receives the value as argument and should return true to
* accept the value and false to reject the value .
*
* @ param string $option The option name
* @ param mixed $allowedValues One or more acceptable values / closures
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws UndefinedOptionsException If the option is undefined
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function addAllowedValues ( $option , $allowedValues = null )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Allowed values cannot be added from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
// BC
if ( is_array ( $option ) && null === $allowedValues ) {
2015-02-28 03:34:22 +00:00
trigger_error ( 'Calling the ' . __METHOD__ . ' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.' , E_USER_DEPRECATED );
2015-02-27 16:11:51 +00:00
2014-10-06 16:43:30 +01:00
foreach ( $option as $optionName => $optionValues ) {
$this -> addAllowedValues ( $optionName , $optionValues );
}
return $this ;
}
if ( ! isset ( $this -> defined [ $option ])) {
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
));
}
2015-03-01 02:42:24 +00:00
if ( ! is_array ( $allowedValues )) {
$allowedValues = array ( $allowedValues );
}
if ( ! isset ( $this -> allowedValues [ $option ])) {
$this -> allowedValues [ $option ] = $allowedValues ;
2014-10-06 16:43:30 +01:00
} else {
2015-03-01 02:42:24 +00:00
$this -> allowedValues [ $option ] = array_merge ( $this -> allowedValues [ $option ], $allowedValues );
2014-10-06 16:43:30 +01:00
}
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
return $this ;
2014-10-21 10:07:51 +01:00
}
2012-04-17 17:14:09 +01:00
/**
2014-10-06 16:43:30 +01:00
* Sets allowed types for an option .
*
* Any type for which a corresponding is_ < type > () function exists is
* acceptable . Additionally , fully - qualified class or interface names may
* be passed .
*
* @ param string $option The option name
* @ param string | string [] $allowedTypes One or more accepted types
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws UndefinedOptionsException If the option is undefined
* @ throws AccessException If called from a lazy option or normalizer
2012-04-17 17:14:09 +01:00
*/
2014-10-06 16:43:30 +01:00
public function setAllowedTypes ( $option , $allowedTypes = null )
2012-04-17 17:14:09 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Allowed types cannot be set from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
// BC
if ( is_array ( $option ) && null === $allowedTypes ) {
2015-02-28 03:34:22 +00:00
trigger_error ( 'Calling the ' . __METHOD__ . ' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.' , E_USER_DEPRECATED );
2015-02-27 16:11:51 +00:00
2014-10-06 16:43:30 +01:00
foreach ( $option as $optionName => $optionTypes ) {
$this -> setAllowedTypes ( $optionName , $optionTypes );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
return $this ;
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
if ( ! isset ( $this -> defined [ $option ])) {
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
));
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
$this -> allowedTypes [ $option ] = ( array ) $allowedTypes ;
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
return $this ;
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Adds allowed types for an option .
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* The types are merged with the allowed types defined previously .
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* Any type for which a corresponding is_ < type > () function exists is
* acceptable . Additionally , fully - qualified class or interface names may
* be passed .
*
* @ param string $option The option name
* @ param string | string [] $allowedTypes One or more accepted types
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws UndefinedOptionsException If the option is undefined
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function addAllowedTypes ( $option , $allowedTypes = null )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Allowed types cannot be added from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
// BC
if ( is_array ( $option ) && null === $allowedTypes ) {
2015-02-28 03:34:22 +00:00
trigger_error ( 'Calling the ' . __METHOD__ . ' method with an array of options is deprecated since version 2.6 and will be removed in 3.0. Use the new signature with a single option instead.' , E_USER_DEPRECATED );
2015-02-27 16:11:51 +00:00
2014-10-06 16:43:30 +01:00
foreach ( $option as $optionName => $optionTypes ) {
$this -> addAllowedTypes ( $optionName , $optionTypes );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
return $this ;
}
if ( ! isset ( $this -> defined [ $option ])) {
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
2014-10-21 10:07:51 +01:00
));
}
2014-10-06 16:43:30 +01:00
if ( ! isset ( $this -> allowedTypes [ $option ])) {
$this -> allowedTypes [ $option ] = ( array ) $allowedTypes ;
} else {
$this -> allowedTypes [ $option ] = array_merge ( $this -> allowedTypes [ $option ], ( array ) $allowedTypes );
}
// Make sure the option is processed
unset ( $this -> resolved [ $option ]);
return $this ;
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Removes the option with the given name .
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* Undefined options are ignored .
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* @ param string | string [] $optionNames One or more option names
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function remove ( $optionNames )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Options cannot be removed from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
foreach (( array ) $optionNames as $option ) {
unset ( $this -> defined [ $option ]);
unset ( $this -> defaults [ $option ]);
unset ( $this -> required [ $option ]);
unset ( $this -> resolved [ $option ]);
unset ( $this -> lazy [ $option ]);
unset ( $this -> normalizers [ $option ]);
unset ( $this -> allowedTypes [ $option ]);
unset ( $this -> allowedValues [ $option ]);
}
return $this ;
}
/**
* Removes all options .
*
2014-10-22 17:49:16 +01:00
* @ return OptionsResolver This instance
2014-10-06 16:43:30 +01:00
*
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-06 16:43:30 +01:00
*/
public function clear ()
{
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Options cannot be cleared from a lazy option or normalizer.' );
2014-10-06 16:43:30 +01:00
}
$this -> defined = array ();
$this -> defaults = array ();
$this -> required = array ();
$this -> resolved = array ();
$this -> lazy = array ();
$this -> normalizers = array ();
$this -> allowedTypes = array ();
$this -> allowedValues = array ();
return $this ;
}
/**
* Merges options with the default values stored in the container and
* validates them .
*
* Exceptions are thrown if :
*
* - Undefined options are passed ;
* - Required options are missing ;
* - Options have invalid types ;
* - Options have invalid values .
*
* @ param array $options A map of option names to values
*
* @ return array The merged and validated options
*
* @ throws UndefinedOptionsException If an option name is undefined
* @ throws InvalidOptionsException If an option doesn ' t fulfill the
* specified validation rules
* @ throws MissingOptionsException If a required option is missing
2014-10-22 17:49:16 +01:00
* @ throws OptionDefinitionException If there is a cyclic dependency between
* lazy options and / or normalizers
2014-10-22 20:14:29 +01:00
* @ throws NoSuchOptionException If a lazy option reads an unavailable option
2014-10-22 17:49:16 +01:00
* @ throws AccessException If called from a lazy option or normalizer
2014-10-06 16:43:30 +01:00
*/
public function resolve ( array $options = array ())
{
2014-10-22 17:49:16 +01:00
if ( $this -> locked ) {
2014-10-22 20:14:29 +01:00
throw new AccessException ( 'Options cannot be resolved from a lazy option or normalizer.' );
2014-10-22 17:49:16 +01:00
}
2014-10-06 16:43:30 +01:00
// Allow this method to be called multiple times
$clone = clone $this ;
// Make sure that no unknown options are passed
$diff = array_diff_key ( $options , $clone -> defined );
if ( count ( $diff ) > 0 ) {
ksort ( $clone -> defined );
ksort ( $diff );
throw new UndefinedOptionsException ( sprintf (
2014-11-22 18:21:28 +00:00
( count ( $diff ) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.' ) . ' Defined options are: "%s".' ,
2014-10-06 16:43:30 +01:00
implode ( '", "' , array_keys ( $diff )),
implode ( '", "' , array_keys ( $clone -> defined ))
));
}
// Override options set by the user
foreach ( $options as $option => $value ) {
$clone -> defaults [ $option ] = $value ;
unset ( $clone -> resolved [ $option ], $clone -> lazy [ $option ]);
}
// Check whether any required option is missing
$diff = array_diff_key ( $clone -> required , $clone -> defaults );
2014-10-21 10:07:51 +01:00
if ( count ( $diff ) > 0 ) {
ksort ( $diff );
throw new MissingOptionsException ( sprintf (
count ( $diff ) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.' ,
implode ( '", "' , array_keys ( $diff ))
));
}
2014-10-06 16:43:30 +01:00
// Lock the container
$clone -> locked = true ;
// Now process the individual options. Use offsetGet(), which resolves
// the option itself and any options that the option depends on
foreach ( $clone -> defaults as $option => $_ ) {
$clone -> offsetGet ( $option );
}
return $clone -> resolved ;
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Returns the resolved value of an option .
*
* @ param string $option The option name
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* @ return mixed The option value
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* @ throws AccessException If accessing this method outside of
* { @ link resolve ()}
2014-10-22 20:14:29 +01:00
* @ throws NoSuchOptionException If the option is not set
* @ throws InvalidOptionsException If the option doesn ' t fulfill the
2014-10-06 16:43:30 +01:00
* specified validation rules
2014-10-22 17:49:16 +01:00
* @ throws OptionDefinitionException If there is a cyclic dependency between
* lazy options and / or normalizers
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function offsetGet ( $option )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( ! $this -> locked ) {
throw new AccessException ( 'Array access is only supported within closures of lazy options and normalizers.' );
}
// Shortcut for resolved options
if ( array_key_exists ( $option , $this -> resolved )) {
return $this -> resolved [ $option ];
}
// Check whether the option is set at all
if ( ! array_key_exists ( $option , $this -> defaults )) {
2014-10-22 20:14:29 +01:00
if ( ! isset ( $this -> defined [ $option ])) {
throw new NoSuchOptionException ( sprintf (
2014-11-22 18:21:28 +00:00
'The option "%s" does not exist. Defined options are: "%s".' ,
2014-10-22 20:14:29 +01:00
$option ,
implode ( '", "' , array_keys ( $this -> defined ))
));
}
throw new NoSuchOptionException ( sprintf (
'The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.' ,
$option
));
2014-10-06 16:43:30 +01:00
}
$value = $this -> defaults [ $option ];
// Resolve the option if the default value is lazily evaluated
if ( isset ( $this -> lazy [ $option ])) {
// If the closure is already being called, we have a cyclic
// dependency
if ( isset ( $this -> calling [ $option ])) {
throw new OptionDefinitionException ( sprintf (
'The options "%s" have a cyclic dependency.' ,
implode ( '", "' , array_keys ( $this -> calling ))
));
}
// The following section must be protected from cyclic
// calls. Set $calling for the current $option to detect a cyclic
// dependency
// BEGIN
$this -> calling [ $option ] = true ;
foreach ( $this -> lazy [ $option ] as $closure ) {
2014-11-21 09:20:58 +00:00
$value = $closure ( $this , $value );
2014-10-06 16:43:30 +01:00
}
unset ( $this -> calling [ $option ]);
// END
}
// Validate the type of the resolved option
if ( isset ( $this -> allowedTypes [ $option ])) {
$valid = false ;
foreach ( $this -> allowedTypes [ $option ] as $type ) {
2014-11-29 09:15:31 +00:00
$type = isset ( self :: $typeAliases [ $type ]) ? self :: $typeAliases [ $type ] : $type ;
2014-10-06 16:43:30 +01:00
if ( function_exists ( $isFunction = 'is_' . $type )) {
if ( $isFunction ( $value )) {
$valid = true ;
break ;
}
continue ;
}
if ( $value instanceof $type ) {
$valid = true ;
break ;
}
}
if ( ! $valid ) {
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 ),
2014-10-22 15:47:08 +01:00
implode ( '" or "' , $this -> allowedTypes [ $option ]),
2014-10-06 16:43:30 +01:00
$this -> formatTypeOf ( $value )
));
}
}
// Validate the value of the resolved option
if ( isset ( $this -> allowedValues [ $option ])) {
$success = false ;
$printableAllowedValues = array ();
foreach ( $this -> allowedValues [ $option ] as $allowedValue ) {
if ( $allowedValue instanceof \Closure ) {
if ( $allowedValue ( $value )) {
$success = true ;
break ;
}
// Don't include closures in the exception message
continue ;
} elseif ( $value === $allowedValue ) {
$success = true ;
break ;
2014-10-21 10:07:51 +01:00
}
2014-10-22 15:47:08 +01:00
$printableAllowedValues [] = $allowedValue ;
2014-10-06 16:43:30 +01:00
}
if ( ! $success ) {
$message = sprintf (
'The option "%s" with value %s is invalid.' ,
$option ,
$this -> formatValue ( $value )
);
if ( count ( $printableAllowedValues ) > 0 ) {
$message .= sprintf (
2014-10-22 15:47:08 +01:00
' Accepted values are: %s.' ,
2014-10-06 16:43:30 +01:00
$this -> formatValues ( $printableAllowedValues )
);
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
throw new InvalidOptionsException ( $message );
}
}
// Normalize the validated option
if ( isset ( $this -> normalizers [ $option ])) {
// If the closure is already being called, we have a cyclic
// dependency
if ( isset ( $this -> calling [ $option ])) {
throw new OptionDefinitionException ( sprintf (
'The options "%s" have a cyclic dependency.' ,
implode ( '", "' , array_keys ( $this -> calling ))
));
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
$normalizer = $this -> normalizers [ $option ];
// The following section must be protected from cyclic
// calls. Set $calling for the current $option to detect a cyclic
// dependency
// BEGIN
$this -> calling [ $option ] = true ;
2014-11-21 09:20:58 +00:00
$value = $normalizer ( $this , $value );
2014-10-06 16:43:30 +01:00
unset ( $this -> calling [ $option ]);
// END
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
// Mark as resolved
$this -> resolved [ $option ] = $value ;
return $value ;
2014-10-21 10:07:51 +01:00
}
/**
2014-10-06 16:43:30 +01:00
* Returns whether a resolved option with the given name exists .
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* @ param string $option The option name
2014-10-21 10:07:51 +01:00
*
2014-10-06 16:43:30 +01:00
* @ return bool Whether the option is set
*
2014-10-22 14:47:58 +01:00
* @ throws AccessException If accessing this method outside of { @ link resolve ()}
*
2014-10-06 16:43:30 +01:00
* @ see \ArrayAccess :: offsetExists ()
2014-10-21 10:07:51 +01:00
*/
2014-10-06 16:43:30 +01:00
public function offsetExists ( $option )
2014-10-21 10:07:51 +01:00
{
2014-10-06 16:43:30 +01:00
if ( ! $this -> locked ) {
throw new AccessException ( 'Array access is only supported within closures of lazy options and normalizers.' );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
return array_key_exists ( $option , $this -> defaults );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Not supported .
*
* @ throws AccessException
*/
public function offsetSet ( $option , $value )
{
throw new AccessException ( 'Setting options via array access is not supported. Use setDefault() instead.' );
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Not supported .
*
* @ throws AccessException
*/
public function offsetUnset ( $option )
{
throw new AccessException ( 'Removing options via array access is not supported. Use remove() instead.' );
}
/**
2014-10-22 14:47:58 +01:00
* Returns the number of set options .
*
* This may be only a subset of the defined options .
*
* @ return int Number of options
*
* @ throws AccessException If accessing this method outside of { @ link resolve ()}
*
* @ see \Countable :: count ()
2014-10-06 16:43:30 +01:00
*/
public function count ()
{
if ( ! $this -> locked ) {
throw new AccessException ( 'Counting is only supported within closures of lazy options and normalizers.' );
}
2014-10-21 10:07:51 +01:00
2014-10-22 14:47:58 +01:00
return count ( $this -> defaults );
2014-10-06 16:43:30 +01:00
}
2014-10-21 10:07:51 +01:00
2014-10-06 16:43:30 +01:00
/**
* Alias of { @ link setDefault ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function set ( $option , $value )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefaults() method instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> setDefault ( $option , $value );
}
/**
* Shortcut for { @ link clear ()} and { @ link setDefaults ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function replace ( array $defaults )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the clear() and setDefaults() methods instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
$this -> clear ();
return $this -> setDefaults ( $defaults );
}
/**
* Alias of { @ link setDefault ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function overload ( $option , $value )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefault() method instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> setDefault ( $option , $value );
}
/**
* Alias of { @ link offsetGet ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function get ( $option )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the ArrayAccess syntax instead to get an option value.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> offsetGet ( $option );
}
/**
* Alias of { @ link offsetExists ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function has ( $option )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the ArrayAccess syntax instead to get an option value.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> offsetExists ( $option );
}
/**
* Shortcut for { @ link clear ()} and { @ link setDefaults ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function replaceDefaults ( array $defaultValues )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the clear() and setDefaults() methods instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
$this -> clear ();
return $this -> setDefaults ( $defaultValues );
}
/**
* Alias of { @ link setDefined ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function setOptional ( array $optionNames )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the setDefined() method instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> setDefined ( $optionNames );
}
/**
* Alias of { @ link isDefined ()} .
*
2014-12-29 23:26:56 +00:00
* @ deprecated since version 2.6 , to be removed in 3.0 .
2014-10-06 16:43:30 +01:00
*/
public function isKnown ( $option )
{
2014-12-21 11:39:54 +00:00
trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the isDefined() method instead.' , E_USER_DEPRECATED );
2014-10-06 16:43:30 +01:00
return $this -> isDefined ( $option );
}
/**
* Returns a string representation of the type of the value .
*
* This method should be used if you pass the type of a value as
* message parameter to a constraint violation . Note that such
* parameters should usually not be included in messages aimed at
* non - technical people .
*
* @ param mixed $value The value to return the type of
*
* @ return string The type of the value
*/
private function formatTypeOf ( $value )
{
return is_object ( $value ) ? get_class ( $value ) : gettype ( $value );
}
/**
* Returns a string representation of the value .
*
* This method returns the equivalent PHP tokens for most scalar types
* ( i . e . " false " for false , " 1 " for 1 etc . ) . Strings are always wrapped
* in double quotes ( " ).
*
* @ param mixed $value The value to format as string
*
* @ return string The string representation of the passed value
*/
private function formatValue ( $value )
{
if ( is_object ( $value )) {
return get_class ( $value );
}
if ( is_array ( $value )) {
return 'array' ;
}
if ( is_string ( $value )) {
return '"' . $value . '"' ;
}
if ( is_resource ( $value )) {
return 'resource' ;
}
if ( null === $value ) {
return 'null' ;
}
if ( false === $value ) {
return 'false' ;
}
if ( true === $value ) {
return 'true' ;
2014-10-21 10:07:51 +01:00
}
2014-10-06 16:43:30 +01:00
return ( string ) $value ;
}
/**
* Returns a string representation of a list of values .
*
* Each of the values is converted to a string using
* { @ link formatValue ()} . The values are then concatenated with commas .
*
* @ param array $values A list of values
*
* @ return string The string representation of the value list
*
* @ see formatValue ()
*/
private function formatValues ( array $values )
{
foreach ( $values as $key => $value ) {
$values [ $key ] = $this -> formatValue ( $value );
}
return implode ( ', ' , $values );
2012-05-23 15:46:54 +01:00
}
2012-04-17 17:14:09 +01:00
}