diff --git a/src/Symfony/Component/OptionsResolver/NestableOptionsResolverInterface.php b/src/Symfony/Component/OptionsResolver/NestableOptionsResolverInterface.php new file mode 100644 index 0000000000000..4eea903efc6c7 --- /dev/null +++ b/src/Symfony/Component/OptionsResolver/NestableOptionsResolverInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +/** + * @author Wouter J + */ +interface NestableOptionsResolverInterface extends OptionsResolverInterface +{ + /** + * Sets nested options resolvers. + * + * @param array $options A list of option names as keys and instances of + * OptionsResolverInterface as values. + */ + public function setNestedOptionsResolver(array $options); +} diff --git a/src/Symfony/Component/OptionsResolver/Options.php b/src/Symfony/Component/OptionsResolver/Options.php index 43c81f0c33403..3b2ab2deb52fd 100644 --- a/src/Symfony/Component/OptionsResolver/Options.php +++ b/src/Symfony/Component/OptionsResolver/Options.php @@ -97,10 +97,10 @@ public function set($option, $value) /** * Sets the normalizer for a given option. * - * Normalizers should be closures with the following signature: + * Normalizers should be valid callables with the following signature: * * - * function (Options $options, $value) + * function (Options $options, $value, $option) * * * This closure will be evaluated once the option is read using @@ -108,14 +108,18 @@ public function set($option, $value) * other options through the passed {@link Options} instance. * * @param string $option The name of the option. - * @param \Closure $normalizer The normalizer. + * @param callable $normalizer The normalizer. * * @throws OptionDefinitionException If options have already been read. * Once options are read, the container * becomes immutable. */ - public function setNormalizer($option, \Closure $normalizer) + public function setNormalizer($option, $normalizer) { + if (!is_callable($normalizer)) { + throw new \InvalidArgumentException('Normalizers should be a valid callable.'); + } + if ($this->reading) { throw new OptionDefinitionException('Normalizers cannot be added anymore once options have been read.'); } @@ -500,11 +504,11 @@ private function normalize($option) throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', $conflicts))); } - /** @var \Closure $normalizer */ + /** @var callable $normalizer */ $normalizer = $this->normalizers[$option]; $this->lock[$option] = true; - $this->options[$option] = $normalizer($this, array_key_exists($option, $this->options) ? $this->options[$option] : null); + $this->options[$option] = call_user_func($normalizer, $this, array_key_exists($option, $this->options) ? $this->options[$option] : null, $option); unset($this->lock[$option]); // The option is now normalized diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 237ab8135f503..7c143283063c1 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -21,7 +21,7 @@ * @author Bernhard Schussek * @author Tobias Schultze */ -class OptionsResolver implements OptionsResolverInterface +class OptionsResolver implements NestableOptionsResolverInterface { /** * The default option values. @@ -53,6 +53,12 @@ class OptionsResolver implements OptionsResolverInterface */ private $allowedTypes = array(); + /** + * A list of the nested option resolvers. + * @var array + */ + private $optionResolvers = array(); + /** * Creates a new instance. */ @@ -181,6 +187,37 @@ public function addAllowedTypes(array $allowedTypes) return $this; } + /** + * {@inheritdoc} + */ + public function setNestedOptionsResolver(array $options) + { + $this->validateOptionsExistence($options); + + foreach ($options as $option => $resolver) { + if (!$resolver instanceof OptionsResolverInterface) { + throw new \InvalidArgumentException('Nested option resolvers have to implement OptionsResolverInterface.'); + } + $this->optionResolvers[$option] = $resolver; + $this->setNormalizers(array($option => array($this, 'nestedNormalizer'))); + } + + return $this; + } + + /** + * This normalizer will be called when an option has a nested options + * resolver. + * + * @param Options $options + * @param null|array $value + * @param string $option + */ + public function nestedNormalizer(Options $options, $value, $option) + { + return $this->optionResolvers[$option]->resolve($value ?: array()); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php index fc3b3fc5d38e3..cad144273b3f5 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php @@ -717,4 +717,54 @@ public function testClone() 'three' => '3', ), $clone->resolve()); } + + public function testNestedResolversForRequiredOption() + { + $this->resolver->setRequired(array('db')); + $this->resolver->setNestedOptionsResolver(array( + 'db' => $this->getNestedResolver(), + )); + + $this->assertEquals(array( + 'db' => array( + 'dsn' => 'sqlite:app.sqlite', + 'user' => 'root', + 'password' => '', + 'port' => 3306, + ), + ), $this->resolver->resolve(array( + 'db' => array( + 'dsn' => 'sqlite:app.sqlite', + ), + ))); + } + + /** + * @expectedException Symfony\Component\OptionsResolver\Exception\MissingOptionsException + * + * Nested options resolvers will always be executed, eventhough the option + * is missing. See {@link https://github.com/symfony/symfony/issues/9174} + */ + public function testNestedResolversForOptionalOption() + { + $this->resolver->setOptional(array('db')); + $this->resolver->setNestedOptionsResolver(array( + 'db' => $this->getNestedResolver(), + )); + + $this->assertEquals(array(), $this->resolver->resolve(array())); + } + + private function getNestedResolver() + { + $nestedResolver = new OptionsResolver(); + $nestedResolver->setDefaults(array( + 'user' => 'root', + 'password' => '', + 'port' => 3306, + )); + $nestedResolver->setRequired(array('dsn')); + + return $nestedResolver; + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy