Skip to content

Commit

Permalink
feature #36345 [OptionsResolver] Improve the deprecation feature by h…
Browse files Browse the repository at this point in the history
…andling package and version (atailouloute)

This PR was merged into the 5.1-dev branch.

Discussion
----------

[OptionsResolver] Improve the deprecation feature by handling package and version

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      |no
| New feature?  | yes
| Deprecations? | yes
| Tickets       |
| License       | MIT
| Doc PR        | TODO

Commits
-------

c3f5e2c [OptionsResolver] Improve the deprecation feature by handling package + version
  • Loading branch information
fabpot committed Apr 8, 2020
2 parents aa44db0 + c3f5e2c commit c6a176d
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 51 deletions.
9 changes: 9 additions & 0 deletions UPGRADE-5.1.md
Expand Up @@ -7,6 +7,7 @@ Config
* The signature of method `NodeDefinition::setDeprecated()` has been updated to `NodeDefinition::setDeprecation(string $package, string $version, string $message)`.
* The signature of method `BaseNode::setDeprecated()` has been updated to `BaseNode::setDeprecation(string $package, string $version, string $message)`.
* Passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node is deprecated
* Deprecated `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead

Console
-------
Expand All @@ -21,6 +22,8 @@ DependencyInjection
* The signature of method `DeprecateTrait::deprecate()` has been updated to `DeprecateTrait::deprecation(string $package, string $version, string $message)`.
* Deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service,
configure them explicitly instead.
* Deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead.
* Deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead.

Dotenv
------
Expand Down Expand Up @@ -86,6 +89,12 @@ Notifier
* [BC BREAK] The `EmailMessage::fromNotification()` and `SmsMessage::fromNotification()`
methods' `$transport` argument was removed.

OptionsResolver
---------------

* The signature of method `OptionsResolver::setDeprecated()` has been updated to `OptionsResolver::setDeprecated(string $option, string $package, string $version, $message)`.
* Deprecated `OptionsResolverIntrospector::getDeprecationMessage()`, use `OptionsResolverIntrospector::getDeprecation()` instead.

PhpUnitBridge
-------------

Expand Down
9 changes: 9 additions & 0 deletions UPGRADE-6.0.md
Expand Up @@ -7,6 +7,7 @@ Config
* The signature of method `NodeDefinition::setDeprecated()` has been updated to `NodeDefinition::setDeprecation(string $package, string $version, string $message)`.
* The signature of method `BaseNode::setDeprecated()` has been updated to `BaseNode::setDeprecation(string $package, string $version, string $message)`.
* Passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node is not supported anymore.
* Removed `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead.

Console
-------
Expand All @@ -21,6 +22,8 @@ DependencyInjection
* The signature of method `DeprecateTrait::deprecate()` has been updated to `DeprecateTrait::deprecation(string $package, string $version, string $message)`.
* Removed the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service,
configure them explicitly instead.
* Removed `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead.
* Removed `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead.

Dotenv
------
Expand Down Expand Up @@ -69,6 +72,12 @@ Messenger
* The signature of method `RetryStrategyInterface::isRetryable()` has been updated to `RetryStrategyInterface::isRetryable(Envelope $message, \Throwable $throwable = null)`.
* The signature of method `RetryStrategyInterface::getWaitingTime()` has been updated to `RetryStrategyInterface::getWaitingTime(Envelope $message, \Throwable $throwable = null)`.

OptionsResolver
---------------

* The signature of method `OptionsResolver::setDeprecated()` has been updated to `OptionsResolver::setDeprecated(string $option, string $package, string $version, $message)`.
* Removed `OptionsResolverIntrospector::getDeprecationMessage()`, use `OptionsResolverIntrospector::getDeprecation()` instead.

PhpUnitBridge
-------------

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Config/CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* updated the signature of method `NodeDefinition::setDeprecated()` to `NodeDefinition::setDeprecation(string $package, string $version, string $message)`
* updated the signature of method `BaseNode::setDeprecated()` to `BaseNode::setDeprecation(string $package, string $version, string $message)`
* deprecated passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node
* deprecated `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead

5.0.0
-----
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Expand Up @@ -14,6 +14,8 @@ CHANGELOG
configure them explicitly instead
* added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+
* added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload
* deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead
* deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead

5.0.0
-----
Expand Down
8 changes: 5 additions & 3 deletions src/Symfony/Component/Form/Console/Descriptor/Descriptor.php
Expand Up @@ -129,7 +129,7 @@ protected function getOptionDefinition(OptionsResolver $optionsResolver, string
'allowedTypes' => 'getAllowedTypes',
'allowedValues' => 'getAllowedValues',
'normalizers' => 'getNormalizers',
'deprecationMessage' => 'getDeprecationMessage',
'deprecation' => 'getDeprecation',
];

foreach ($map as $key => $method) {
Expand All @@ -140,8 +140,10 @@ protected function getOptionDefinition(OptionsResolver $optionsResolver, string
}
}

if (isset($definition['deprecationMessage']) && \is_string($definition['deprecationMessage'])) {
$definition['deprecationMessage'] = strtr($definition['deprecationMessage'], ['%name%' => $option]);
if (isset($definition['deprecation']) && isset($definition['deprecation']['message']) && \is_string($definition['deprecation']['message'])) {
$definition['deprecationMessage'] = strtr($definition['deprecation']['message'], ['%name%' => $option]);
$definition['deprecationPackage'] = $definition['deprecation']['package'];
$definition['deprecationVersion'] = $definition['deprecation']['version'];
}

return $definition;
Expand Down
Expand Up @@ -110,6 +110,8 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio
if ($definition['deprecated']) {
$map = [
'Deprecated' => 'deprecated',
'Deprecation package' => 'deprecationPackage',
'Deprecation version' => 'deprecationVersion',
'Deprecation message' => 'deprecationMessage',
];
}
Expand Down
Expand Up @@ -198,7 +198,7 @@ public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('foo');
$resolver->setDefined('bar');
$resolver->setDeprecated('bar');
$resolver->setDeprecated('bar', 'vendor/package', '1.1');
$resolver->setDefault('empty_data', function (Options $options) {
$foo = $options['foo'];

Expand Down
Expand Up @@ -150,7 +150,7 @@ public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('foo');
$resolver->setDefined('bar');
$resolver->setDeprecated('bar');
$resolver->setDeprecated('bar', 'vendor/package', '1.1');
$resolver->setDefault('empty_data', function (Options $options, $value) {
$foo = $options['foo'];

Expand Down
Expand Up @@ -3,6 +3,10 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (bar)

--------------------- -----------------------------------
Deprecated true
--------------------- -----------------------------------
Deprecation package "vendor/package"
--------------------- -----------------------------------
Deprecation version "1.1"
--------------------- -----------------------------------
Deprecation message "The option "bar" is deprecated."
--------------------- -----------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/OptionsResolver/CHANGELOG.md
Expand Up @@ -6,6 +6,8 @@ CHANGELOG

* added fluent configuration of options using `OptionResolver::define()`
* added `setInfo()` and `getInfo()` methods
* updated the signature of method `OptionsResolver::setDeprecated()` to `OptionsResolver::setDeprecation(string $option, string $package, string $version, $message)`
* deprecated `OptionsResolverIntrospector::getDeprecationMessage()`, use `OptionsResolverIntrospector::getDeprecation()` instead

5.0.0
-----
Expand Down
Expand Up @@ -100,8 +100,20 @@ public function getNormalizers(string $option): array
* @return string|\Closure
*
* @throws NoConfigurationException on no configured deprecation
*
* @deprecated since Symfony 5.1, use "getDeprecation()" instead.
*/
public function getDeprecationMessage(string $option)
{
trigger_deprecation('symfony/options-resolver', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);

return $this->getDeprecation($option)['message'];
}

/**
* @throws NoConfigurationException on no configured deprecation
*/
public function getDeprecation(string $option): array
{
return ($this->get)('deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option));
}
Expand Down
23 changes: 19 additions & 4 deletions src/Symfony/Component/OptionsResolver/OptionConfigurator.php
Expand Up @@ -84,13 +84,28 @@ public function define(string $option): self
/**
* Marks this option as deprecated.
*
* @return $this
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string|\Closure $message The deprecation message to use
*
* @param string|\Closure $deprecationMessage
* @return $this
*/
public function deprecated($deprecationMessage = 'The option "%name%" is deprecated.'): self
public function deprecated(/*string $package, string $version, $message = 'The option "%name%" is deprecated.'*/): self
{
$this->resolver->setDeprecated($this->name, $deprecationMessage);
$args = \func_get_args();

if (\func_num_args() < 2) {
trigger_deprecation('symfony/options-resolver', '5.1', 'The signature of method "%s()" requires 2 new arguments: "string $package, string $version", not defining them is deprecated.', __METHOD__);

$message = $args[0] ?? 'The option "%name%" is deprecated.';
$package = (string) $version = '';
} else {
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) ($args[2] ?? 'The option "%name%" is deprecated.');
}

$this->resolver->setDeprecated($this->name, $package, $version, $message);

return $this;
}
Expand Down
48 changes: 34 additions & 14 deletions src/Symfony/Component/OptionsResolver/OptionsResolver.php
Expand Up @@ -421,9 +421,11 @@ public function isNested(string $option): bool
* passed to the closure is the value of the option after validating it
* and before normalizing it.
*
* @param string|\Closure $deprecationMessage
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string|\Closure $message The deprecation message to use
*/
public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self
public function setDeprecated(string $option/*, string $package, string $version, $message = 'The option "%name%" is deprecated.' */): self
{
if ($this->locked) {
throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.');
Expand All @@ -433,16 +435,33 @@ public function setDeprecated(string $option, $deprecationMessage = 'The option
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$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".', get_debug_type($deprecationMessage)));
$args = \func_get_args();

if (\func_num_args() < 3) {
trigger_deprecation('symfony/options-resolver', '5.1', 'The signature of method "%s()" requires 2 new arguments: "string $package, string $version", not defining them is deprecated.', __METHOD__);

$message = $args[1] ?? 'The option "%name%" is deprecated.';
$package = $version = '';
} else {
$package = $args[1];
$version = $args[2];
$message = $args[3] ?? 'The option "%name%" is deprecated.';
}

if (!\is_string($message) && !$message instanceof \Closure) {
throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message)));
}

// ignore if empty string
if ('' === $deprecationMessage) {
if ('' === $message) {
return $this;
}

$this->deprecated[$option] = $deprecationMessage;
$this->deprecated[$option] = [
'package' => $package,
'version' => $version,
'message' => $message,
];

// Make sure the option is processed
unset($this->resolved[$option]);
Expand Down Expand Up @@ -903,8 +922,8 @@ public function offsetGet($option, bool $triggerDeprecation = true)

// Shortcut for resolved options
if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) {
if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option])) {
trigger_deprecation('', '', strtr($this->deprecated[$option], ['%name%' => $option]));
if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option]['message'])) {
trigger_deprecation($this->deprecated[$option]['package'], $this->deprecated[$option]['version'], strtr($this->deprecated[$option]['message'], ['%name%' => $option]));
}

return $this->resolved[$option];
Expand Down Expand Up @@ -1048,26 +1067,27 @@ public function offsetGet($option, bool $triggerDeprecation = true)
// Check whether the option is deprecated
// and it is provided by the user or is being called from a lazy evaluation
if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option])))) {
$deprecationMessage = $this->deprecated[$option];
$deprecation = $this->deprecated[$option];
$message = $this->deprecated[$option]['message'];

if ($deprecationMessage instanceof \Closure) {
if ($message instanceof \Closure) {
// If the closure is already being called, we have a cyclic dependency
if (isset($this->calling[$option])) {
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
}

$this->calling[$option] = true;
try {
if (!\is_string($deprecationMessage = $deprecationMessage($this, $value))) {
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($deprecationMessage)));
if (!\is_string($message = $message($this, $value))) {
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message)));
}
} finally {
unset($this->calling[$option]);
}
}

if ('' !== $deprecationMessage) {
trigger_deprecation('', '', strtr($deprecationMessage, ['%name%' => $option]));
if ('' !== $message) {
trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option]));
}
}

Expand Down
Expand Up @@ -213,6 +213,9 @@ public function testGetNormalizersThrowsOnNotDefinedOption()
$debug->getNormalizers('foo');
}

/**
* @group legacy
*/
public function testGetDeprecationMessage()
{
$resolver = new OptionsResolver();
Expand All @@ -223,6 +226,9 @@ public function testGetDeprecationMessage()
$this->assertSame('The option "foo" is deprecated.', $debug->getDeprecationMessage('foo'));
}

/**
* @group legacy
*/
public function testGetClosureDeprecationMessage()
{
$resolver = new OptionsResolver();
Expand All @@ -233,6 +239,34 @@ public function testGetClosureDeprecationMessage()
$this->assertSame($closure, $debug->getDeprecationMessage('foo'));
}

public function testGetDeprecation()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$resolver->setDeprecated('foo', 'vendor/package', '1.1', 'The option "foo" is deprecated.');

$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame([
'package' => 'vendor/package',
'version' => '1.1',
'message' => 'The option "foo" is deprecated.',
], $debug->getDeprecation('foo'));
}

public function testGetClosureDeprecation()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$resolver->setDeprecated('foo', 'vendor/package', '1.1', $closure = function (Options $options, $value) {});

$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame([
'package' => 'vendor/package',
'version' => '1.1',
'message' => $closure,
], $debug->getDeprecation('foo'));
}

public function testGetDeprecationMessageThrowsOnNoConfiguredValue()
{
$this->expectException('Symfony\Component\OptionsResolver\Exception\NoConfigurationException');
Expand All @@ -241,7 +275,7 @@ public function testGetDeprecationMessageThrowsOnNoConfiguredValue()
$resolver->setDefined('foo');

$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDeprecationMessage('foo'));
$debug->getDeprecation('foo');
}

public function testGetDeprecationMessageThrowsOnNotDefinedOption()
Expand All @@ -251,6 +285,6 @@ public function testGetDeprecationMessageThrowsOnNotDefinedOption()
$resolver = new OptionsResolver();

$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDeprecationMessage('foo'));
$debug->getDeprecation('foo');
}
}

0 comments on commit c6a176d

Please sign in to comment.