Add ability to deprecate options
This commit is contained in:
parent
5abffbbd07
commit
f8746ce8bd
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* added `setDeprecated` and `isDeprecated` methods
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\OptionsResolver;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Exception\AccessException;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
|
||||
use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException;
|
||||
@ -75,6 +76,11 @@ class OptionsResolver implements Options
|
||||
*/
|
||||
private $calling = array();
|
||||
|
||||
/**
|
||||
* A list of deprecated options.
|
||||
*/
|
||||
private $deprecated = array();
|
||||
|
||||
/**
|
||||
* Whether the instance is locked for reading.
|
||||
*
|
||||
@ -348,6 +354,57 @@ class OptionsResolver implements Options
|
||||
return array_keys($this->defined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecates an option, allowed types or values.
|
||||
*
|
||||
* Instead of passing the message, you may also pass a closure with the
|
||||
* following signature:
|
||||
*
|
||||
* function ($value) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* The closure receives the value as argument and should return a string.
|
||||
* Returns an empty string to ignore the option deprecation.
|
||||
*
|
||||
* The closure is invoked when {@link resolve()} is called. The parameter
|
||||
* passed to the closure is the value of the option after validating it
|
||||
* and before normalizing it.
|
||||
*
|
||||
* @param string|\Closure $deprecationMessage
|
||||
*/
|
||||
public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self
|
||||
{
|
||||
if ($this->locked) {
|
||||
throw new AccessException('Options cannot be deprecated 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 (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage)));
|
||||
}
|
||||
|
||||
// ignore if empty string
|
||||
if ('' === $deprecationMessage) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->deprecated[$option] = $deprecationMessage;
|
||||
|
||||
// Make sure the option is processed
|
||||
unset($this->resolved[$option]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isDeprecated(string $option): bool
|
||||
{
|
||||
return isset($this->deprecated[$option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the normalizer for an option.
|
||||
*
|
||||
@ -620,6 +677,7 @@ class OptionsResolver implements Options
|
||||
$this->normalizers = array();
|
||||
$this->allowedTypes = array();
|
||||
$this->allowedValues = array();
|
||||
$this->deprecated = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -836,6 +894,19 @@ class OptionsResolver implements Options
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the option is deprecated
|
||||
if (isset($this->deprecated[$option])) {
|
||||
$deprecationMessage = $this->deprecated[$option];
|
||||
|
||||
if ($deprecationMessage instanceof \Closure && !\is_string($deprecationMessage = $deprecationMessage($value))) {
|
||||
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", returns an empty string to ignore.', \gettype($deprecationMessage)));
|
||||
}
|
||||
|
||||
if ('' !== $deprecationMessage) {
|
||||
@trigger_error(strtr($deprecationMessage, array('%name%' => $option)), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the validated option
|
||||
if (isset($this->normalizers[$option])) {
|
||||
// If the closure is already being called, we have a cyclic
|
||||
|
@ -450,6 +450,187 @@ class OptionsResolverTest extends TestCase
|
||||
$this->assertFalse($this->resolver->isDefined('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
|
||||
*/
|
||||
public function testFailIfSetDeprecatedFromLazyOption()
|
||||
{
|
||||
$this->resolver
|
||||
->setDefault('bar', 'baz')
|
||||
->setDefault('foo', function (Options $options) {
|
||||
$options->setDeprecated('bar');
|
||||
})
|
||||
->resolve()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
|
||||
*/
|
||||
public function testSetDeprecatedFailsIfUnknownOption()
|
||||
{
|
||||
$this->resolver->setDeprecated('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage Invalid type for deprecation message argument, expected string or \Closure, but got "boolean".
|
||||
*/
|
||||
public function testSetDeprecatedFailsIfInvalidDeprecationMessageType()
|
||||
{
|
||||
$this->resolver
|
||||
->setDefined('foo')
|
||||
->setDeprecated('foo', true)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", returns an empty string to ignore.
|
||||
*/
|
||||
public function testLazyDeprecationFailsIfInvalidDeprecationMessageType()
|
||||
{
|
||||
$this->resolver
|
||||
->setDefault('foo', true)
|
||||
->setDeprecated('foo', function ($value) {
|
||||
return false;
|
||||
})
|
||||
;
|
||||
$this->resolver->resolve();
|
||||
}
|
||||
|
||||
public function testIsDeprecated()
|
||||
{
|
||||
$this->resolver
|
||||
->setDefined('foo')
|
||||
->setDeprecated('foo')
|
||||
;
|
||||
$this->assertTrue($this->resolver->isDeprecated('foo'));
|
||||
}
|
||||
|
||||
public function testIsNotDeprecatedIfEmptyString()
|
||||
{
|
||||
$this->resolver
|
||||
->setDefined('foo')
|
||||
->setDeprecated('foo', '')
|
||||
;
|
||||
$this->assertFalse($this->resolver->isDeprecated('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDeprecationData
|
||||
*/
|
||||
public function testDeprecationMessages(\Closure $configureOptions, array $options, ?array $expectedError)
|
||||
{
|
||||
error_clear_last();
|
||||
set_error_handler(function () { return false; });
|
||||
$e = error_reporting(0);
|
||||
|
||||
$configureOptions($this->resolver);
|
||||
$this->resolver->resolve($options);
|
||||
|
||||
error_reporting($e);
|
||||
restore_error_handler();
|
||||
|
||||
$lastError = error_get_last();
|
||||
unset($lastError['file'], $lastError['line']);
|
||||
|
||||
$this->assertSame($expectedError, $lastError);
|
||||
}
|
||||
|
||||
public function provideDeprecationData()
|
||||
{
|
||||
yield 'It deprecates an option with default message' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefined(array('foo', 'bar'))
|
||||
->setDeprecated('foo')
|
||||
;
|
||||
},
|
||||
array('foo' => 'baz'),
|
||||
array(
|
||||
'type' => E_USER_DEPRECATED,
|
||||
'message' => 'The option "foo" is deprecated.',
|
||||
),
|
||||
);
|
||||
|
||||
yield 'It deprecates an option with custom message' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefined('foo')
|
||||
->setDefault('bar', function (Options $options) {
|
||||
return $options['foo'];
|
||||
})
|
||||
->setDeprecated('foo', 'The option "foo" is deprecated, use "bar" option instead.')
|
||||
;
|
||||
},
|
||||
array('foo' => 'baz'),
|
||||
array(
|
||||
'type' => E_USER_DEPRECATED,
|
||||
'message' => 'The option "foo" is deprecated, use "bar" option instead.',
|
||||
),
|
||||
);
|
||||
|
||||
yield 'It deprecates a missing option with default value' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefaults(array('foo' => null, 'bar' => null))
|
||||
->setDeprecated('foo')
|
||||
;
|
||||
},
|
||||
array('bar' => 'baz'),
|
||||
array(
|
||||
'type' => E_USER_DEPRECATED,
|
||||
'message' => 'The option "foo" is deprecated.',
|
||||
),
|
||||
);
|
||||
|
||||
yield 'It deprecates allowed type and value' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefault('foo', null)
|
||||
->setAllowedTypes('foo', array('null', 'string', \stdClass::class))
|
||||
->setDeprecated('foo', function ($value) {
|
||||
if ($value instanceof \stdClass) {
|
||||
return sprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', \stdClass::class);
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
;
|
||||
},
|
||||
array('foo' => new \stdClass()),
|
||||
array(
|
||||
'type' => E_USER_DEPRECATED,
|
||||
'message' => 'Passing an instance of "stdClass" to option "foo" is deprecated, pass its FQCN instead.',
|
||||
),
|
||||
);
|
||||
|
||||
yield 'It ignores deprecation for missing option without default value' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefined(array('foo', 'bar'))
|
||||
->setDeprecated('foo')
|
||||
;
|
||||
},
|
||||
array('bar' => 'baz'),
|
||||
null,
|
||||
);
|
||||
|
||||
yield 'It ignores deprecation if closure returns an empty string' => array(
|
||||
function (OptionsResolver $resolver) {
|
||||
$resolver
|
||||
->setDefault('foo', null)
|
||||
->setDeprecated('foo', function ($value) {
|
||||
return '';
|
||||
})
|
||||
;
|
||||
},
|
||||
array('foo' => Bar::class),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
|
||||
*/
|
||||
|
Reference in New Issue
Block a user