From 1ff56407e149023932822ba9433f656ff70f2d70 Mon Sep 17 00:00:00 2001 From: Lorenzo Millucci Date: Fri, 4 Oct 2019 08:29:30 +0200 Subject: [PATCH] [OptionsResolver] Add new OptionConfigurator class to define options with fluent interface --- .../Component/OptionsResolver/CHANGELOG.md | 5 + .../OptionsResolver/OptionConfigurator.php | 127 ++++++++++++++++++ .../OptionsResolver/OptionsResolver.php | 14 +- .../Tests/OptionsResolverTest.php | 32 +++++ 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/OptionsResolver/OptionConfigurator.php diff --git a/src/Symfony/Component/OptionsResolver/CHANGELOG.md b/src/Symfony/Component/OptionsResolver/CHANGELOG.md index 7518482c7ee2..9f62de110f21 100644 --- a/src/Symfony/Component/OptionsResolver/CHANGELOG.md +++ b/src/Symfony/Component/OptionsResolver/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.1.0 +----- + + * added fluent configuration of options using `OptionResolver::define()` + 5.0.0 ----- diff --git a/src/Symfony/Component/OptionsResolver/OptionConfigurator.php b/src/Symfony/Component/OptionsResolver/OptionConfigurator.php new file mode 100644 index 000000000000..085be8907fee --- /dev/null +++ b/src/Symfony/Component/OptionsResolver/OptionConfigurator.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\OptionsResolver; + +use Symfony\Component\OptionsResolver\Exception\AccessException; + +final class OptionConfigurator +{ + private $name; + private $resolver; + + public function __construct(string $name, OptionsResolver $resolver) + { + $this->name = $name; + $this->resolver = $resolver; + $this->resolver->setDefined($name); + } + + /** + * Adds allowed types for this option. + * + * @param string ...$types One or more accepted types + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedTypes(string ...$types): self + { + $this->resolver->setAllowedTypes($this->name, $types); + + return $this; + } + + /** + * Sets allowed values for this option. + * + * @param mixed ...$values One or more acceptable values/closures + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function allowedValues(...$values): self + { + $this->resolver->setAllowedValues($this->name, $values); + + return $this; + } + + /** + * Sets the default value for this option. + * + * @param mixed $value The default value of the option + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function default($value): self + { + $this->resolver->setDefault($this->name, $value); + + return $this; + } + + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): self + { + return $this->resolver->define($option); + } + + /** + * Marks this option as deprecated. + * + * @return $this + * + * @param string|\Closure $deprecationMessage + */ + public function deprecated($deprecationMessage = 'The option "%name%" is deprecated.'): self + { + $this->resolver->setDeprecated($this->name, $deprecationMessage); + + return $this; + } + + /** + * Sets the normalizer for this option. + * + * @param \Closure $normalizer The normalizer + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function normalize(\Closure $normalizer): self + { + $this->resolver->setNormalizer($this->name, $normalizer); + + return $this; + } + + /** + * Marks this option as required. + * + * @return $this + * + * @throws AccessException If called from a lazy option or normalizer + */ + public function required(): self + { + $this->resolver->setRequired($this->name); + + return $this; + } +} diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index fb3bed6a44d5..9687fde06e56 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -703,6 +703,18 @@ public function addAllowedTypes(string $option, $allowedTypes) return $this; } + /** + * Defines an option configurator with the given name. + */ + public function define(string $option): OptionConfigurator + { + if (isset($this->defined[$option])) { + throw new OptionDefinitionException(sprintf('The options "%s" is already defined.', $option)); + } + + return new OptionConfigurator($option, $this); + } + /** * Removes the option with the given name. * @@ -830,7 +842,7 @@ public function resolve(array $options = []) * Returns the resolved value of an option. * * @param string $option The option name - * @param bool $triggerDeprecation Whether to trigger the deprecation or not + * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default) * * @return mixed The option value * diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php index 8e3c1d3331cd..52bbb812ed4e 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; +use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -2378,4 +2379,35 @@ public function testAccessToParentOptionFromNestedNormalizerAndLazyOption() ]; $this->assertSame($expectedOptions, $actualOptions); } + + public function testFailsIfOptionIsAlreadyDefined() + { + $this->expectException('Symfony\Component\OptionsResolver\Exception\OptionDefinitionException'); + $this->expectExceptionMessage('The options "foo" is already defined.'); + $this->resolver->define('foo'); + $this->resolver->define('foo'); + } + + public function testResolveOptionsDefinedByOptionConfigurator() + { + $this->resolver->define('foo') + ->required() + ->deprecated() + ->default('bar') + ->allowedTypes('string', 'bool') + ->allowedValues('bar', 'zab') + ->normalize(static function (Options $options, $value) { + return $value; + }) + ; + $introspector = new OptionsResolverIntrospector($this->resolver); + + $this->assertTrue(true, $this->resolver->isDefined('foo')); + $this->assertTrue(true, $this->resolver->isDeprecated('foo')); + $this->assertTrue(true, $this->resolver->hasDefault('foo')); + $this->assertSame('bar', $introspector->getDefault('foo')); + $this->assertSame(['string', 'bool'], $introspector->getAllowedTypes('foo')); + $this->assertSame(['bar', 'zab'], $introspector->getAllowedValues('foo')); + $this->assertCount(1, $introspector->getNormalizers('foo')); + } }