[OptionsResolver] Add a new method addNormalizer and normalization hierarchy
This commit is contained in:
parent
3165d4be71
commit
cf41254223
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* added `OptionsResolver::addNormalizer` method
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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']));
|
||||
|
Reference in New Issue
Block a user