The OptionsResolver Component
Edit this pageWarning: You are browsing the documentation for Symfony 2.2, which is no longer maintained.
Read the updated version of this page for Symfony 6.3 (the current stable version).
The OptionsResolver Component
The OptionsResolver component helps you configure objects with option arrays. It supports default values, option constraints and lazy options.
Installation
You can install the component in 2 different ways:
- Install it via Composer (
symfony/options-resolver
on Packagist); - Use the official Git repository (https://github.com/symfony/OptionsResolver).
Usage
Imagine you have a Mailer
class which has 2 options: host
and
password
. These options are going to be handled by the OptionsResolver
Component.
First, create the Mailer
class:
1 2 3 4 5 6 7 8
class Mailer
{
protected $options;
public function __construct(array $options = array())
{
}
}
You could of course set the $options
value directly on the property. Instead,
use the OptionsResolver class
and let it resolve the options by calling
resolve().
The advantages of doing this will become more obvious as you continue:
1 2 3 4 5 6 7 8 9
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->options = $resolver->resolve($options);
}
The options property now is a well defined array with all resolved options readily available:
1 2 3 4 5 6 7 8 9 10
// ...
public function getHost()
{
return $this->options['host'];
}
public function getPassword()
{
return $this->options['password'];
}
Configuring the OptionsResolver
Now, try to actually use the class:
1 2 3 4
$mailer = new Mailer(array(
'host' => 'smtp.example.org',
'password' => 'pa$$word',
));
Right now, you'll receive a
InvalidOptionsException,
which tells you that the options host
and password
do not exist.
This is because you need to configure the OptionsResolver
first, so it
knows which options should be resolved.
Tip
To check if an option exists, you can use the isKnown() function.
A best practice is to put the configuration in a method (e.g.
setDefaultOptions
). You call this method in the constructor to configure
the OptionsResolver
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class Mailer
{
protected $options;
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ... configure the resolver, you will learn this in the sections below
}
}
Required Options
The host
option is required: the class can't work without it. You can set
the required options by calling
setRequired():
1 2 3 4 5
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('host'));
}
You are now able to use the class without errors:
1 2 3 4 5
$mailer = new Mailer(array(
'host' => 'smtp.example.org',
));
echo $mailer->getHost(); // 'smtp.example.org'
If you don't pass a required option, a MissingOptionsException will be thrown.
To determine if an option is required, you can use the isRequired() method.
Optional Options
Sometimes, an option can be optional (e.g. the password
option in the
Mailer
class). You can configure these options by calling
setOptional():
1 2 3 4 5 6 7
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setOptional(array('password'));
}
Set Default Values
Most of the optional options have a default value. You can configure these options by calling setDefaults():
1 2 3 4 5 6 7 8 9
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setDefaults(array(
'username' => 'root',
));
}
This would add a third option - username
- and give it a default value
of root
. If the user passes in a username
option, that value will
override this default. You don't need to configure username
as an optional
option. The OptionsResolver
already knows that options with a default
value are optional.
Default Values that depend on another Option
Suppose you add a port
option to the Mailer
class, whose default
value you guess based on the host. You can do that easily by using a
closure as the default value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setDefaults(array(
'port' => function (Options $options) {
if (in_array($options['host'], array('127.0.0.1', 'localhost'))) {
return 80;
}
return 25;
},
));
}
Caution
The first argument of the closure must be typehinted as Options
,
otherwise it is considered as the value.
Overwriting Default Values
A previously set default value can be overwritten by invoking setDefaults() again. When using a closure as the new value it is passed 2 arguments:
$options
: an Options instance with all the other default options$previousValue
: the previous set default value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setDefaults(array(
'encryption' => 'ssl',
'host' => 'localhost',
));
// ...
$resolver->setDefaults(array(
'encryption' => 'tls', // simple overwrite
'host' => function (Options $options, $previousValue) {
return 'localhost' == $previousValue ? '127.0.0.1' : $previousValue;
},
));
}
Tip
If the previous default value is calculated by an expensive closure and
you don't need access to it, you can use the
replaceDefaults()
method instead. It acts like setDefaults
but simply erases the
previous value to improve performance. This means that the previous
default value is not available when overwriting with another closure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setDefaults(array(
'encryption' => 'ssl',
'heavy' => function (Options $options) {
// Some heavy calculations to create the $result
return $result;
},
));
$resolver->replaceDefaults(array(
'encryption' => 'tls', // simple overwrite
'heavy' => function (Options $options) {
// $previousValue not available
// ...
return $someOtherResult;
},
));
}
Note
Existing option keys that you do not mention when overwriting are preserved.
Configure allowed Values
Not all values are valid values for options. Suppose the Mailer
class has
a transport
option, it can only be one of sendmail
, mail
or
smtp
. You can configure these allowed values by calling
setAllowedValues():
1 2 3 4 5 6 7 8 9
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setAllowedValues(array(
'transport' => array('sendmail', 'mail', 'smtp'),
));
}
There is also an addAllowedValues() method, which you can use if you want to add an allowed value to the previously set allowed values.
Configure allowed Types
You can also specify allowed types. For instance, the port
option can
be anything, but it must be an integer. You can configure these types by calling
setAllowedTypes():
1 2 3 4 5 6 7 8 9
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setAllowedTypes(array(
'port' => 'integer',
));
}
Possible types are the ones associated with the is_*
PHP functions or a
class name. You can also pass an array of types as the value. For instance,
array('null', 'string')
allows port
to be null
or a string
.
There is also an addAllowedTypes() method, which you can use to add an allowed type to the previous allowed types.
Normalize the Options
Some values need to be normalized before you can use them. For instance,
pretend that the host
should always start with http://
. To do that,
you can write normalizers. These closures will be executed after all options
are passed and should return the normalized value. You can configure these
normalizers by calling
setNormalizers():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setNormalizers(array(
'host' => function (Options $options, $value) {
if ('http://' !== substr($value, 0, 7)) {
$value = 'http://'.$value;
}
return $value;
},
));
}
You see that the closure also gets an $options
parameter. Sometimes, you
need to use the other options for normalizing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// ...
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ...
$resolver->setNormalizers(array(
'host' => function (Options $options, $value) {
if (!in_array(substr($value, 0, 7), array('http://', 'https://'))) {
if ($options['ssl']) {
$value = 'https://'.$value;
} else {
$value = 'http://'.$value;
}
}
return $value;
},
));
}