Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3b7280e

Browse files
committed
Add ability to deprecate options
1 parent 226e2f3 commit 3b7280e

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-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: 66 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,52 @@ 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 closures 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+
* or anything else 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+
$this->deprecated[$option] = $deprecationMessage;
391+
392+
// Make sure the option is processed
393+
unset($this->resolved[$option]);
394+
395+
return $this;
396+
}
397+
398+
public function isDeprecated(string $option): bool
399+
{
400+
return isset($this->deprecated[$option]);
401+
}
402+
351403
/**
352404
* Sets the normalizer for an option.
353405
*
@@ -620,6 +672,7 @@ public function clear()
620672
$this->normalizers = array();
621673
$this->allowedTypes = array();
622674
$this->allowedValues = array();
675+
$this->deprecated = array();
623676

624677
return $this;
625678
}
@@ -836,6 +889,19 @@ public function offsetGet($option)
836889
}
837890
}
838891

892+
// Check whether the option is deprecated
893+
if (isset($this->deprecated[$option])) {
894+
$deprecationMessage = $this->deprecated[$option];
895+
896+
if ($deprecationMessage instanceof \Closure) {
897+
$deprecationMessage = $deprecationMessage($value);
898+
}
899+
900+
if (\is_string($deprecationMessage)) {
901+
@trigger_error(strtr($deprecationMessage, array('%name%' => $option)), E_USER_DEPRECATED);
902+
}
903+
}
904+
839905
// Normalize the validated option
840906
if (isset($this->normalizers[$option])) {
841907
// If the closure is already being called, we have a cyclic

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

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

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

0 commit comments

Comments
 (0)