[OptionsResolver] Add a new method addNormalizer and normalization hierarchy

This commit is contained in:
Yonel Ceruto 2019-02-19 15:58:35 -05:00 committed by Fabien Potencier
parent 3165d4be71
commit cf41254223
5 changed files with 160 additions and 5 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
4.3.0
-----
* added `OptionsResolver::addNormalizer` method
4.2.0
-----

View File

@ -84,6 +84,14 @@ class OptionsResolverIntrospector
* @throws NoConfigurationException on no configured normalizer
*/
public function getNormalizer(string $option): \Closure
{
return current($this->getNormalizers($option));
}
/**
* @throws NoConfigurationException when no normalizer is configured
*/
public function getNormalizers(string $option): array
{
return ($this->get)('normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option));
}

View File

@ -57,7 +57,7 @@ class OptionsResolver implements Options
/**
* A list of normalizer closures.
*
* @var \Closure[]
* @var \Closure[][]
*/
private $normalizers = [];
@ -484,7 +484,56 @@ class OptionsResolver implements Options
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
}
$this->normalizers[$option] = $normalizer;
$this->normalizers[$option] = [$normalizer];
// Make sure the option is processed
unset($this->resolved[$option]);
return $this;
}
/**
* Adds a normalizer for an option.
*
* The normalizer should be a closure with the following signature:
*
* function (Options $options, $value): mixed {
* // ...
* }
*
* The closure is invoked when {@link resolve()} is called. The closure
* has access to the resolved values of other options through the passed
* {@link Options} instance.
*
* The second parameter passed to the closure is the value of
* the option.
*
* The resolved option value is set to the return value of the closure.
*
* @param string $option The option name
* @param \Closure $normalizer The normalizer
* @param bool $forcePrepend If set to true, prepend instead of appending
*
* @return $this
*
* @throws UndefinedOptionsException If the option is undefined
* @throws AccessException If called from a lazy option or normalizer
*/
public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = false): self
{
if ($this->locked) {
throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.');
}
if (!isset($this->defined[$option])) {
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
}
if ($forcePrepend) {
array_unshift($this->normalizers[$option], $normalizer);
} else {
$this->normalizers[$option][] = $normalizer;
}
// Make sure the option is processed
unset($this->resolved[$option]);
@ -966,15 +1015,15 @@ class OptionsResolver implements Options
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
}
$normalizer = $this->normalizers[$option];
// The following section must be protected from cyclic
// calls. Set $calling for the current $option to detect a cyclic
// dependency
// BEGIN
$this->calling[$option] = true;
try {
$value = $normalizer($this, $value);
foreach ($this->normalizers[$option] as $normalizer) {
$value = $normalizer($this, $value);
}
} finally {
unset($this->calling[$option]);
}

View File

@ -201,6 +201,42 @@ class OptionsResolverIntrospectorTest extends TestCase
$this->assertSame('bar', $debug->getNormalizer('foo'));
}
public function testGetNormalizers()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$resolver->addNormalizer('foo', $normalizer1 = function () {});
$resolver->addNormalizer('foo', $normalizer2 = function () {});
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame([$normalizer1, $normalizer2], $debug->getNormalizers('foo'));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException
* @expectedExceptionMessage No normalizer was set for the "foo" option.
*/
public function testGetNormalizersThrowsOnNoConfiguredValue()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$debug = new OptionsResolverIntrospector($resolver);
$debug->getNormalizers('foo');
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
* @expectedExceptionMessage The option "foo" does not exist.
*/
public function testGetNormalizersThrowsOnNotDefinedOption()
{
$resolver = new OptionsResolver();
$debug = new OptionsResolverIntrospector($resolver);
$debug->getNormalizers('foo');
}
public function testGetDeprecationMessage()
{
$resolver = new OptionsResolver();

View File

@ -1554,6 +1554,63 @@ class OptionsResolverTest extends TestCase
$this->assertEmpty($this->resolver->resolve());
}
public function testAddNormalizerReturnsThis()
{
$this->resolver->setDefault('foo', 'bar');
$this->assertSame($this->resolver, $this->resolver->addNormalizer('foo', function () {}));
}
public function testAddNormalizerClosure()
{
// defined by superclass
$this->resolver->setDefault('foo', 'bar');
$this->resolver->setNormalizer('foo', function (Options $options, $value) {
return '1st-normalized-'.$value;
});
// defined by subclass
$this->resolver->addNormalizer('foo', function (Options $options, $value) {
return '2nd-normalized-'.$value;
});
$this->assertEquals(['foo' => '2nd-normalized-1st-normalized-bar'], $this->resolver->resolve());
}
public function testForcePrependNormalizerClosure()
{
// defined by superclass
$this->resolver->setDefault('foo', 'bar');
$this->resolver->setNormalizer('foo', function (Options $options, $value) {
return '2nd-normalized-'.$value;
});
// defined by subclass
$this->resolver->addNormalizer('foo', function (Options $options, $value) {
return '1st-normalized-'.$value;
}, true);
$this->assertEquals(['foo' => '2nd-normalized-1st-normalized-bar'], $this->resolver->resolve());
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
*/
public function testAddNormalizerFailsIfUnknownOption()
{
$this->resolver->addNormalizer('foo', function () {});
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
*/
public function testFailIfAddNormalizerFromLazyOption()
{
$this->resolver->setDefault('foo', function (Options $options) {
$options->addNormalizer('foo', function () {});
});
$this->resolver->resolve();
}
public function testSetDefaultsReturnsThis()
{
$this->assertSame($this->resolver, $this->resolver->setDefaults(['foo', 'bar']));