10000 Add ability to deprecate options · symfony/symfony@bbed8b3 · GitHub
[go: up one dir, main page]

Skip to content

Commit bbed8b3

Browse files
committed
Add ability to deprecate options
1 parent be1b37f commit bbed8b3

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

src/Symfony/Component/OptionsResolver/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.2.0
5+
-----
6+
7+
* added `setDeprecated` and `isDeprecated` methods
8+
49
3.4.0
510
-----
611

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\OptionsResolver;
1313

1414
use Symfony\Component\OptionsResolver\Exception\AccessException;
15+
use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException;
1516
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
1617
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
1718
use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException;
@@ -75,6 +76,11 @@ class OptionsResolver implements Options
7576
*/
7677
private $calling = array();
7778

79+
/**
80+
* A list of deprecated options.
81+
*/
82+
private $deprecated = array();
83+
7884
/**
7985
* Whether the instance is locked for reading.
8086
*
@@ -348,6 +354,57 @@ public function getDefinedOptions()
348354
return array_keys($this->defined);
349355
}
350356

357+
/**
358+
* Deprecates an option, allowed types or values.
359+
*
360+
* Instead of passing the message, you may also pass a closure with the
361+
* following signature:
362+
*
363+
* function ($value) {
364+
* // ...
365+
* }
366+
*
367+
* The closure receives the value as argument and should return a string.
368+
* Returns an empty string to ignore the option deprecation.
369+
*
370+
* The closure is invoked when {@link resolve()} is called. The parameter
371+
* passed to the closure is the value of the option after validating it
372+
* and before normalizing it.
373+
*
374+
* @param string|\Closure $deprecationMessage
375+
*/
376+
public function setDeprecated(string $option, $deprecationMessage = 'The option "%name%" is deprecated.'): self
377+
{
378+
if ($this->locked) {
379+
throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.');
380+
}
381+
382+
if (!isset($this->defined[$option])) {
383+
throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $option, implode('", "', array_keys($this->defined))));
384+
}
385+
386+
if (!\is_string($deprecationMessage) && !$deprecationMessage instanceof \Closure) {
387+
throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', \gettype($deprecationMessage)));
388+
}
389+
390+
// ignore if empty string
391+
if ('' === $deprecationMessage) {
392+
return $this;
393+
}
394+
395+
$this->deprecated[$option] = $deprecationMessage;
396+
397+
// Make sure the option is processed
398+
unset($this->resolved[$option]);
399+
400+
return $this;
401+
}
402+
403+
public function isDeprecated(string $option): bool
404+
{
405+
return isset($this->deprecated[$option]);
406+
}
407+
351408
/**
352409
* Sets the normalizer for an option.
353410
*
@@ -620,6 +677,7 @@ public function clear()
620677
$this->normalizers = array();
621678
$this->allowedTypes = array();
622679
$this->allowedValues = array();
680+
$this->deprecated = array();
623681

624682
return $this;
625683
}
@@ -836,6 +894,19 @@ public function offsetGet($option)
836894
}
837895
}
838896

897+
// Check whether the option is deprecated
898+
if (isset($this->deprecated[$option])) {
899+
$deprecationMessage = $this->deprecated[$option];
900+
901+
if ($deprecationMessage instanceof \Closure && !\is_string($deprecationMessage = $deprecationMessage($value))) {
902+
throw new InvalidArgumentException(sprintf('Invalid type for deprecation message, expected string but got "%s", returns an empty string to ignore.', \gettype($deprecationMessage)));
903+
}
904+
905+
if ('' !== $deprecationMessage) {
906+
@trigger_error(strtr($deprecationMessage, array('%name%' => $option)), E_USER_DEPRECATED);
907+
}
908+
}
909+
839910
// Normalize the validated option
840911
if (isset($this->normalizers[$option])) {
841912
// If the closure is already being called, we have a cyclic

src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,187 @@ public function testClearedOptionsAreNotDefined()
474474
$this->assertFalse($this->resolver->isDefined('foo'));
475475
}
476476

477+
/**
478+
* @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
479+
*/
480+
public function testFailIfSetDeprecatedFromLazyOption()
481+
{
482+
$this->resolver
483+
->setDefault('bar', 'baz')
484+
->setDefault('foo', function (Options $options) {
485+
$options->setDeprecated('bar');
486+
})
487+
->resolve()
488+
;
489+
}
490+
491+
/**
492+
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
493+
*/
494+
public function testSetDeprecatedFailsIfUnknownOption()
495+
{
496+
$this->resolver->setDeprecated('foo');
497+
}
498+
499+
/**
500+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
501+
* @expectedExceptionMessage Invalid type for deprecation message argument, expected string or \Closure, but got "boolean".
502+
*/
503+
public function testSetDeprecatedFailsIfInvalidDeprecationMessageType()
504+
{
505+
$this->resolver
506+
->setDefined('foo')
507+
->setDeprecated('foo', true)
508+
;
509+
}
510+
511+
/**
512+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
513+
* @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", returns an empty string to ignore.
514+
*/
515+
public function testLazyDeprecationFailsIfInvalidDeprecationMessageType()
516+
{
517+
$this->resolver
518+
->setDefault('foo', true)
519+
->setDeprecated('foo', function ($value) {
520+
return false;
521+
})
522+
;
523+
$this->resolver->resolve();
524+
}
525+
526+
public function testIsDeprecated()
527+
{
528+
$this->resolver
529+
->setDefined('foo')
530+
->setDeprecated('foo')
531+
;
532+
$this->assertTrue($this->resolver->isDeprecated('foo'));
533+
}
534+
535+
public function testIsNotDeprecatedIfEmptyString()
536+
{
537+
$this->resolver
538+
->setDefined('foo')
539+
->setDeprecated('foo', '')
540+
;
541+
$this->assertFalse($this->resolver->isDeprecated('foo'));
542+
}
543+
544+
/**
545+
* @dataProvider provideDeprecationData
546+
*/
547+
public function testDeprecationMessages(\Closure $configureOptions, array $options, ?array $expectedError)
548+
{
549+
error_clear_last();
550+
set_error_handler(function () { return false; });
551+
$e = error_reporting(0);
552+
553+
$configureOptions($this->resolver);
554+
$this->resolver->resolve($options);
555+
556+
error_reporting($e);
557+
restore_error_handler();
558+
559+
$lastError = error_get_last();
560+
unset($lastError['file'], $lastError['line']);
561+
562+
$this->assertSame($expectedError, $lastError);
563+
}
564+
565+
public function provideDeprecationData()
566+
{
567+
yield 'It deprecates an option with default message' => array(
568+
function (OptionsResolver $resolver) {
569+
$resolver
570+
->setDefined(array('foo', 'bar'))
571+
->setDeprecated('foo')
572+
;
573+
},
574+
array('foo' => 'baz'),
575+
array(
576+
'type' => E_USER_DEPRECATED,
577+
'message' => 'The option "foo" is deprecated.',
578+
),
579+
);
580+
581+
yield 'It deprecates an option with custom message' => array(
582+
function (OptionsResolver $resolver) {
583+
$resolver
584+
->setDefined('foo')
585+
->setDefault('bar', function (Options $options) {
586+
return $options['foo'];
587+
})
588+
->setDeprecated('foo', 'The option "foo" is deprecated, use "bar" option instead.')
589+
;
590+
},
591+
array('foo' => 'baz'),
592+
array(
593+
'type' => E_USER_DEPRECATED,
594+
'message' => 'The option "foo" is deprecated, use "bar" option instead.',
595+
),
596+
);
597+
598+
yield 'It deprecates a missing option with default value' => array(
599+
function (OptionsResolver $resolver) {
600+
$resolver
601+
->setDefaults(array('foo' => null, 'bar' => null))
602+
->setDeprecated('foo')
603+
;
604+
},
605+
array('bar' => 'baz'),
606+
array(
607+
'type' => E_USER_DEPRECATED,
608+
'message' => 'The option "foo" is deprecated.',
609+
),
610+
);
611+
612+
yield 'It deprecates allowed type and value' => array(
613+
function (OptionsResolver $resolver) {
614+
$resolver
615+
->setDefault('foo', null)
616+
->setAllowedTypes('foo', array('null', 'string', Bar::class))
617+
->setDeprecated('foo', function ($value) {
618+
if ($value instanceof Bar) {
619+
return sprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', Bar::class);
620+
}
621+
622+
return '';
623+
})
624+
;
625+
},
626+
array('foo' => new Bar()),
627+
array(
628+
'type' => E_USER_DEPRECATED,
629+
'message' => 'Passing an instance of "Symfony\Component\OptionsResolver\Tests\Bar" to option "foo" is deprecated, pass its FQCN instead.',
630+
),
631+
);
632+
633+
yield 'It ignores deprecation for missing option without default value' => array(
634+
function (OptionsResolver $resolver) {
635+
$resolver
636+
->setDefined(array('foo', 'bar'))
637+
->setDeprecated('foo')
638+
;
639+
},
640+
array('bar' => 'baz'),
641+
null,
642+
);
643+
644+
yield 'It ignores deprecation if closure returns an empty string' => array(
645+
function (OptionsResolver $resolver) {
646+
$resolver
647+
->setDefault('foo', null)
648+
->setDeprecated('foo', function ($value) {
649+
return '';
650+
})
651+
;
652+
},
653+
array('foo' => Bar::class),
654+
null,
655+
);
656+
}
657+
477658
////////////////////////////////////////////////////////////////////////////
478659
// setAllowedTypes()
479660
////////////////////////////////////////////////////////////////////////////
@@ -1651,3 +1832,7 @@ public function testCountFailsOutsideResolve()
16511832
count($this->resolver);
16521833
}
16531834
}
1835+
1836+
class Bar
1837+
{
1838+
}

0 commit comments

Comments
 (0)
0