-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[WIP] Bootstrapped Options Resolver Component #2353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
.. index:: | ||
single: Options Resolver | ||
single: Components; OptionsResolver | ||
|
||
The OptionsResolver Component | ||
============================= | ||
|
||
The OptionsResolver Component helps you at configuring objects with option | ||
arrays. It supports default values, option constraints and lazy options. | ||
|
||
.. versionadded:: 2.1 | ||
The OptionsResolver Component is new in Symfony2.1 | ||
|
||
Installation | ||
------------ | ||
|
||
You can install the component in several different ways: | ||
|
||
* Use the official Git repository (https://github.com/symfony/OptionsResolver | ||
* :doc:`Install it via Composer</components/using_components>` (``symfony/options-resolver`` on `Packagist`_) | ||
|
||
Usage | ||
----- | ||
|
||
Imagine you have a ``Person`` class which has 2 options: ``firstName`` and | ||
``lastName``. These options are going to be handled by the OptionsResolver | ||
Component. | ||
|
||
First of all, you should create some basic skeleton:: | ||
|
||
class Person | ||
{ | ||
protected $options; | ||
|
||
public function __construct(array $options = array()) | ||
{ | ||
} | ||
} | ||
|
||
Now, you should handle the ``$options`` parameter with the | ||
:class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` class. To do | ||
this, you should instantiate the ``OptionsResolver`` class and let it resolve | ||
the options by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`:: | ||
|
||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
||
// ... | ||
public function __construct(array $options = array()) | ||
{ | ||
$resolver = new OptionsResolver(); | ||
|
||
$this->options = $resolver->resolve($options); | ||
} | ||
|
||
The ``$options`` property is an instance of | ||
:class:`Symfony\\Component\\OptionsResolver\\Options`, which implements | ||
:phpclass:`ArrayAccess`, :phpclass:`Iterator` and :phpclass:`Countable`. That | ||
means you can handle it as a normal array:: | ||
|
||
// ... | ||
public function getFirstName() | ||
{ | ||
return $this->options['firstName']; | ||
} | ||
|
||
public function getFullName() | ||
{ | ||
$name = $this->options['firstName']; | ||
|
||
if (isset($this->options['lastName'])) { | ||
$name .= ' '.$this->options['lastName']; | ||
} | ||
|
||
return $name; | ||
} | ||
|
||
Let's use the class:: | ||
|
||
$person = new Person(array( | ||
'firstName' => 'Wouter', | ||
'lastName' => 'de Jong', | ||
)); | ||
|
||
echo $person->getFirstName(); | ||
|
||
As you see, you get a | ||
:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException` | ||
which tells you that the options ``firstName`` and ``lastName`` not exists. | ||
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 | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isKnown` isser. | ||
|
||
A best practise is to put the configuration in a method (e.g. | ||
``setDefaultOptions``). You call this method in the constructor to configure | ||
the ``OptionsResolver`` class:: | ||
|
||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
use Symfony\Component\OptionsResolver\OptionsResolverInterface; | ||
|
||
class Person | ||
{ | ||
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 ``firstName`` option is required; the class can't work without that | ||
option. You can set the required options by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
$resolver->setRequired(array('firstName')); | ||
} | ||
|
||
You are now able to use the class without errors:: | ||
|
||
$person = new Person(array( | ||
'firstName' => 'Wouter', | ||
)); | ||
|
||
echo $person->getFirstName(); // 'Wouter' | ||
|
||
If you don't pass a required option, an | ||
:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException` | ||
will be thrown. | ||
|
||
To determine if an option is required, you can use the | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` | ||
method. | ||
|
||
Optional Options | ||
---------------- | ||
|
||
Sometimes, an option can be optional (e.g. the ``lastName`` option in the | ||
``Person`` class). You can configure these options by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptional`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setOptional(array('lastName')); | ||
} | ||
|
||
Set Default Values | ||
------------------ | ||
|
||
Most of the optional options have a default value. You can configure these | ||
options by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefaults`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setDefaults(array( | ||
'age' => 0, | ||
)); | ||
} | ||
|
||
The default age will be ``0`` now. When the user specifies an age, it gets | ||
replaced. You don't need to configure ``age`` as an optional option. The | ||
``OptionsResolver`` already knows that options with a default value are | ||
optional. | ||
|
||
The ``OptionsResolver`` component also has an | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::replaceDefaults` | ||
method. This can be used to override the previous default value. The closure | ||
that is passed has 2 parameters: | ||
|
||
* ``$options`` (an :class:`Symfony\\Component\\OptionsResolver\\Options` | ||
instance), with all the default options | ||
* ``$value``, the previous set default value | ||
|
||
Default values that depend on another option | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
If you add a ``gender`` option to the ``Person`` class, it should get a | ||
default value which guess the gender based on the first name. You can do that | ||
easily by using a Closure as default value:: | ||
|
||
use Symfony\Component\OptionsResolver\Options; | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setDefaults(array( | ||
'gender' => function (Options $options) { | ||
if (GenderGuesser::isMale($options['firstName'])) { | ||
return 'male'; | ||
} | ||
|
||
return 'female'; | ||
}, | ||
)); | ||
} | ||
|
||
.. caution:: | ||
|
||
The first argument of the Closure must be typehinted as `Options`, | ||
otherwise it is considered as the value. | ||
|
||
Configure allowed values | ||
------------------------ | ||
|
||
Not all values are valid values for options. For instance, the ``gender`` | ||
option can only be ``female`` or ``male``. You can configure these allowed | ||
values by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setAllowedValues(array( | ||
'gender' => array('male', 'female'), | ||
)); | ||
} | ||
|
||
There is also a | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues` | ||
method, which you can use if you want to add an allowed value to the previous | ||
setted allowed values. | ||
|
||
Configure allowed Types | ||
~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
You can also specify allowed types. For instance, the ``firstName`` option can | ||
be anything, but it must be a string. You can configure these types by calling | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setAllowedTypes(array( | ||
'firstName' => 'string', | ||
)); | ||
} | ||
|
||
Possible types are the one associated with the ``is_*`` php functions or a | ||
class name. You can also pass an array of types as value. For instance, | ||
``array('null', 'string')`` allows ``firstName`` to be ``null`` or a | ||
``string``. | ||
|
||
There is also a | ||
:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes` | ||
method, which you can use to add an allowed type to the previous allowed types. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should explain the allowed values here: a PHP type with the naming used by the associated And you should document that you can also pass an array of allowed types to allow several ones, for instance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, thank you! |
||
Normalize the Options | ||
--------------------- | ||
|
||
Some values needs to be normalized before you can use them. For instance, the | ||
``firstName`` should always start with an uppercase letter. To do that, you can | ||
write normalizers. These Closures will be executed after all options are | ||
passed and return the normalized value. You can configure these normalizers by | ||
calling | ||
:method:`Symfony\\Components\\OptionsResolver\\OptionsResolver::setNormalizers`:: | ||
|
||
// ... | ||
protected function setDefaultOptions(OptionsResolverInterface $resolver) | ||
{ | ||
// ... | ||
|
||
$resolver->setNormalizers(array( | ||
'firstName' => function (Options $options, $value) { | ||
return ucfirst($value); | ||
}, | ||
)); | ||
} | ||
|
||
You see that the closure also get an ``$options`` parameter. Sometimes, you | ||
need to use the other options for normalizing. | ||
|
||
.. _Packagist: https://packagist.org/packages/symfony/options-resolver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this last part (starting with
The closure that is passed...
) is very clear. I think we need an example here to show what you can actually do and how this closure is used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add it if you (or anyone) can give me a good example (I can't find a simple good 'somewhat real use-case' example for this one).