diff --git a/components/options_resolver.rst b/components/options_resolver.rst index ea1372f4ea6..7e85bf3c3cf 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -5,7 +5,9 @@ The OptionsResolver Component ============================= - The OptionsResolver component is `array_replace()` on steroids. + The OptionsResolver component is :phpfunction:`array_replace` on steroids. + It allows you to create an options system with required options, defaults, + validation (type, value), normalization and more. Installation ------------ @@ -20,8 +22,8 @@ Notes on Previous Versions .. versionadded:: 2.6 This documentation was written for Symfony 2.6 and later. If you use an older - version, please read the corresponding documentation using the version - drop-down on the upper right. + version, please `read the Symfony 2.5 documentation`_. For a list of changes, + see the `CHANGELOG`_. Usage ----- @@ -48,29 +50,35 @@ check which options are set:: public function sendMail($from, $to) { $mail = ...; + $mail->setHost(isset($this->options['host']) ? $this->options['host'] : 'smtp.example.org'); + $mail->setUsername(isset($this->options['username']) ? $this->options['username'] : 'user'); + $mail->setPassword(isset($this->options['password']) ? $this->options['password'] : 'pa$$word'); + $mail->setPort(isset($this->options['port']) ? $this->options['port'] : 25); + // ... } } This boilerplate is hard to read and repetitive. Also, the default values of the -options are buried in the business logic of your code. We can use +options are buried in the business logic of your code. Use the :phpfunction:`array_replace` to fix that:: class Mailer { // ... + public function __construct(array $options = array()) { $this->options = array_replace(array( @@ -83,7 +91,7 @@ options are buried in the business logic of your code. We can use } Now all four options are guaranteed to be set. But what happens if the user of -the ``Mailer`` class does a mistake? +the ``Mailer`` class makes a mistake? .. code-block:: php @@ -91,19 +99,19 @@ the ``Mailer`` class does a mistake? 'usernme' => 'johndoe', )); -No error will be shown. In the best case, the bug will be appear during testing. -The developer will possibly spend a lot of time looking for the problem. In the -worst case, however, the bug won't even appear and will be deployed to the live -system. +No error will be shown. In the best case, the bug will appear during testing, +but the developer will spend time looking for the problem. In the worst case, +the bug might not appear until it's deployed to the live system. -Let's use the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` -class to fix this problem:: +Fortunately, the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` +class helps you to fix this problem:: use Symfony\Component\OptionsResolver\Options; class Mailer { // ... + public function __construct(array $options = array()) { $resolver = new OptionsResolver(); @@ -136,6 +144,7 @@ code:: class Mailer { // ... + public function sendMail($from, $to) { $mail = ...; @@ -153,6 +162,7 @@ It's a good practice to split the option configuration into a separate method:: class Mailer { // ... + public function __construct(array $options = array()) { $resolver = new OptionsResolver(); @@ -164,10 +174,10 @@ It's a good practice to split the option configuration into a separate method:: protected function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'host' => 'smtp.example.org', - 'username' => 'user', - 'password' => 'pa$$word', - 'port' => 25, + 'host' => 'smtp.example.org', + 'username' => 'user', + 'password' => 'pa$$word', + 'port' => 25, 'encryption' => null, )); } @@ -196,12 +206,13 @@ Required Options If an option must be set by the caller, pass that option to :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`. -For example, let's make the ``host`` option required:: +For example, to make the ``host`` option required, you can do:: // ... class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -210,8 +221,8 @@ For example, let's make the ``host`` option required:: } .. versionadded:: 2.6 - Before Symfony 2.6, `setRequired()` accepted only arrays. Since then, single - option names can be passed as well. + As of Symfony 2.6, ``setRequired()`` accepts both an array of options or a + single option. Prior to 2.6, you could only pass arrays. If you omit a required option, a :class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException` @@ -229,6 +240,7 @@ one required option:: class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -268,14 +280,15 @@ retrieve the names of all required options:: If you want to check whether a required option is still missing from the default options, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isMissing`. -The difference to :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` -is that this method will return false for required options that have already +The difference between this and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` +is that this method will return false if a required option has already been set:: // ... class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -320,6 +333,7 @@ correctly. To validate the types of the options, call class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -329,8 +343,8 @@ correctly. To validate the types of the options, call } For each option, you can define either just one type or an array of acceptable -types. You can pass any type for which an ``is_()`` method is defined. -Additionally, you may pass fully qualified class or interface names. +types. You can pass any type for which an ``is_()`` function is defined +in PHP. Additionally, you may pass fully qualified class or interface names. If you pass an invalid option now, an :class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException` @@ -348,9 +362,7 @@ to add additional allowed types without erasing the ones already set. .. versionadded:: 2.6 Before Symfony 2.6, `setAllowedTypes()` and `addAllowedTypes()` expected - the values to be given as an array mapping option names to allowed types: - - .. code-block:: php + the values to be given as an array mapping option names to allowed types:: $resolver->setAllowedTypes(array('port' => array('null', 'int'))); @@ -360,13 +372,14 @@ Value Validation Some options can only take one of a fixed list of predefined values. For example, suppose the ``Mailer`` class has a ``transport`` option which can be one of ``sendmail``, ``mail`` and ``smtp``. Use the method -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues` to verify -that the passed option contains one of these values:: +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues` +to verify that the passed option contains one of these values:: // ... class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -420,9 +433,11 @@ option. You can configure a normalizer by calling class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... + $resolver->setNormalizer('host', function ($options, $value) { if ('http://' !== substr($value, 0, 7)) { $value = 'http://'.$value; @@ -467,12 +482,12 @@ Default Values that Depend on another Option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Suppose you want to set the default value of the ``port`` option based on the -encryption chosen by the user of the ``Mailer`` class. More precisely, we want +encryption chosen by the user of the ``Mailer`` class. More precisely, you want to set the port to ``465`` if SSL is used and to ``25`` otherwise. -You can implement this feature by passing a closure as default value of the -``port`` option. The closure receives the options as argument. Based on these -options, you can return the desired default value:: +You can implement this feature by passing a closure as the default value of +the ``port`` option. The closure receives the options as argument. Based on +these options, you can return the desired default value:: use Symfony\Component\OptionsResolver\Options; @@ -498,7 +513,7 @@ options, you can return the desired default value:: .. caution:: The argument of the callable must be type hinted as ``Options``. Otherwise, - the callable is considered as the default value of the option. + the callable itself is considered as the default value of the option. .. note:: @@ -546,8 +561,10 @@ Options without Default Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In some cases, it is useful to define an option without setting a default value. -Mostly, you will need this when you want to know whether an option was passed -or not. If you set a default value for that option, this is not possible:: +This is useful if you need to know whether or not the user *actually* set +an option or not. For example, if you set the default value for an option, +it's not possible to know whether the user passed this value or if it simply +comes from the default:: // ... class Mailer @@ -584,6 +601,7 @@ be included in the resolved options if it was actually passed to class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) { // ... @@ -637,6 +655,8 @@ let you find out which options are defined:: // ... class GoogleMailer extends Mailer { + // ... + protected function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); @@ -671,10 +691,10 @@ can change your code to do the configuration only once per class:: public function __construct(array $options = array()) { - // Are we a Mailer, a GoogleMailer, ... ? + // What type of Mailer is this, a Mailer, a GoogleMailer, ... ? $class = get_class($this); - // Did we call configureOptions() before for this class? + // Was configureOptions() executed before for this class? if (!isset(self::$resolversByClass[$class])) { self::$resolversByClass[$class] = new OptionsResolver(); $this->configureOptions(self::$resolversByClass[$class]); @@ -693,14 +713,14 @@ Now the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` instance will be created once per class and reused from that on. Be aware that this may lead to memory leaks in long-running applications, if the default options contain references to objects or object graphs. If that's the case for you, implement a -method ``clearDefaultOptions()`` and call it periodically:: +method ``clearOptionsConfig()`` and call it periodically:: // ... class Mailer { private static $resolversByClass = array(); - public static function clearDefaultOptions() + public static function clearOptionsConfig() { self::$resolversByClass = array(); } @@ -713,3 +733,5 @@ options in your code. .. _Packagist: https://packagist.org/packages/symfony/options-resolver .. _Form component: http://symfony.com/doc/current/components/form/introduction.html +.. _CHANGELOG: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/OptionsResolver/CHANGELOG.md#260 +.. _`read the Symfony 2.5 documentation`: http://symfony.com/doc/2.5/components/options_resolver.html