Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit76d3589

Browse files
feature#27277 [OptionsResolver] Introduce ability to deprecate options, allowed types and values (yceruto)
This PR was merged into the 4.2-dev branch.Discussion----------[OptionsResolver] Introduce ability to deprecate options, allowed types and values| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#27216| License | MIT| Doc PR |symfony/symfony-docs#9859**Deprecating an option**```php$resolver = (new OptionsResolver()) ->setDefined(['foo', 'bar']) ->setDeprecated('foo');$resolver->resolve(['foo' => 'baz']); // PHP Deprecated: The option "foo" is deprecated.```With custom message:```php$resolver = (new OptionsResolver()) ->setDefined('foo') ->setDefault('bar', function (Options $options) { return $options['foo']; }) ->setDeprecated('foo', 'The option "foo" is deprecated, use "bar" option instead.');$resolver->resolve(['foo' => 'baz']); // PHP Deprecated: The option "foo" is deprecated, use "bar" option instead.$resolver->resolve(['bar' => 'baz']); // OK.```**Deprecating allowed types**```php$resolver = (new OptionsResolver()) ->setDefault('type', null) ->setAllowedTypes('type', ['null', 'string', FormTypeInterface::class]) ->setDeprecated('type', function ($value) { if ($value instanceof FormTypeInterface) { return sprintf('Passing an instance of "%s" to option "type" is deprecated, pass its FQCN instead.', FormTypeInterface::class); } });$resolver->resolve(['type' => new ChoiceType()]); // PHP Deprecated: Passing an instance of "Symfony\Component\Form\FormTypeInterface" to option "type" is deprecated, pass its FQCN instead.$resolver->resolve(['type' => ChoiceType::class]); // OK.```The closure is invoked when `resolve()` is called. The closure must return a string (the deprecation message) or an empty string to ignore the option deprecation.Multiple types and normalizer:```php$resolver = (new OptionsResolver()) ->setDefault('percent', 0.0) ->setAllowedTypes('percent', ['null', 'int', 'float']) ->setDeprecated('percent', function ($value) { if (null === $value) { return 'Passing "null" to option "percent" is deprecated, pass a float number instead.'; } if (is_int($value)) { return sprintf('Passing an integer "%d" to option "percent" is deprecated, pass a float number instead.', $value); } }) ->setNormalizer('percent', function (Options $options, $value) { return (float) $value; });$resolver->resolve(['percent' => null]); // PHP Deprecated: Passing "null" to option "percent" is deprecated, pass a float number instead.$resolver->resolve(['percent' => 20]); // PHP Deprecated: Passing an integer "20" to option "percent" is deprecated, pass a float number instead.$resolver->resolve(['percent' => 20.0]); // OK.```The parameter passed to the closure is the value of the option after validating it and before normalizing it.**Deprecating allowed values**```php$resolver = (new OptionsResolver()) ->setDefault('percent', 0.0) ->setAllowedTypes('percent', 'float') ->setDeprecated('percent', function ($value) { if ($value < 0) { return 'Passing a number less than 0 to option "percent" is deprecated.'; } });$resolver->resolve(['percent' => -50.0]); // PHP Deprecated: Passing a number less than 0 to option "percent" is deprecated.```Commits-------f8746ce Add ability to deprecate options
2 parentsd148fa7 +f8746ce commit76d3589

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-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
namespaceSymfony\Component\OptionsResolver;
1313

1414
useSymfony\Component\OptionsResolver\Exception\AccessException;
15+
useSymfony\Component\OptionsResolver\Exception\InvalidArgumentException;
1516
useSymfony\Component\OptionsResolver\Exception\InvalidOptionsException;
1617
useSymfony\Component\OptionsResolver\Exception\MissingOptionsException;
1718
useSymfony\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
returnarray_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+
publicfunctionsetDeprecated(string$option,$deprecationMessage ='The option "%name%" is deprecated.'):self
377+
{
378+
if ($this->locked) {
379+
thrownewAccessException('Options cannot be deprecated from a lazy option or normalizer.');
380+
}
381+
382+
if (!isset($this->defined[$option])) {
383+
thrownewUndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".',$option,implode('", "',array_keys($this->defined))));
384+
}
385+
386+
if (!\is_string($deprecationMessage) && !$deprecationMessageinstanceof \Closure) {
387+
thrownewInvalidArgumentException(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+
publicfunctionisDeprecated(string$option):bool
404+
{
405+
returnisset($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 ($deprecationMessageinstanceof \Closure && !\is_string($deprecationMessage =$deprecationMessage($value))) {
902+
thrownewInvalidOptionsException(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: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,187 @@ public function testClearedOptionsAreNotDefined()
450450
$this->assertFalse($this->resolver->isDefined('foo'));
451451
}
452452

453+
/**
454+
* @expectedException \Symfony\Component\OptionsResolver\Exception\AccessException
455+
*/
456+
publicfunctiontestFailIfSetDeprecatedFromLazyOption()
457+
{
458+
$this->resolver
459+
->setDefault('bar','baz')
460+
->setDefault('foo',function (Options$options) {
461+
$options->setDeprecated('bar');
462+
})
463+
->resolve()
464+
;
465+
}
466+
467+
/**
468+
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
469+
*/
470+
publicfunctiontestSetDeprecatedFailsIfUnknownOption()
471+
{
472+
$this->resolver->setDeprecated('foo');
473+
}
474+
475+
/**
476+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
477+
* @expectedExceptionMessage Invalid type for deprecation message argument, expected string or \Closure, but got "boolean".
478+
*/
479+
publicfunctiontestSetDeprecatedFailsIfInvalidDeprecationMessageType()
480+
{
481+
$this->resolver
482+
->setDefined('foo')
483+
->setDeprecated('foo',true)
484+
;
485+
}
486+
487+
/**
488+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
489+
* @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", returns an empty string to ignore.
490+
*/
491+
publicfunctiontestLazyDeprecationFailsIfInvalidDeprecationMessageType()
492+
{
493+
$this->resolver
494+
->setDefault('foo',true)
495+
->setDeprecated('foo',function ($value) {
496+
returnfalse;
497+
})
498+
;
499+
$this->resolver->resolve();
500+
}
501+
502+
publicfunctiontestIsDeprecated()
503+
{
504+
$this->resolver
505+
->setDefined('foo')
506+
->setDeprecated('foo')
507+
;
508+
$this->assertTrue($this->resolver->isDeprecated('foo'));
509+
}
510+
511+
publicfunctiontestIsNotDeprecatedIfEmptyString()
512+
{
513+
$this->resolver
514+
->setDefined('foo')
515+
->setDeprecated('foo','')
516+
;
517+
$this->assertFalse($this->resolver->isDeprecated('foo'));
518+
}
519+
520+
/**
521+
* @dataProvider provideDeprecationData
522+
*/
523+
publicfunctiontestDeprecationMessages(\Closure$configureOptions,array$options, ?array$expectedError)
524+
{
525+
error_clear_last();
526+
set_error_handler(function () {returnfalse; });
527+
$e =error_reporting(0);
528+
529+
$configureOptions($this->resolver);
530+
$this->resolver->resolve($options);
531+
532+
error_reporting($e);
533+
restore_error_handler();
534+
535+
$lastError =error_get_last();
536+
unset($lastError['file'],$lastError['line']);
537+
538+
$this->assertSame($expectedError,$lastError);
539+
}
540+
541+
publicfunctionprovideDeprecationData()
542+
{
543+
yield'It deprecates an option with default message' =>array(
544+
function (OptionsResolver$resolver) {
545+
$resolver
546+
->setDefined(array('foo','bar'))
547+
->setDeprecated('foo')
548+
;
549+
},
550+
array('foo' =>'baz'),
551+
array(
552+
'type' =>E_USER_DEPRECATED,
553+
'message' =>'The option "foo" is deprecated.',
554+
),
555+
);
556+
557+
yield'It deprecates an option with custom message' =>array(
558+
function (OptionsResolver$resolver) {
559+
$resolver
560+
->setDefined('foo')
561+
->setDefault('bar',function (Options$options) {
562+
return$options['foo'];
563+
})
564+
->setDeprecated('foo','The option "foo" is deprecated, use "bar" option instead.')
565+
;
566+
},
567+
array('foo' =>'baz'),
568+
array(
569+
'type' =>E_USER_DEPRECATED,
570+
'message' =>'The option "foo" is deprecated, use "bar" option instead.',
571+
),
572+
);
573+
574+
yield'It deprecates a missing option with default value' =>array(
575+
function (OptionsResolver$resolver) {
576+
$resolver
577+
->setDefaults(array('foo' =>null,'bar' =>null))
578+
->setDeprecated('foo')
579+
;
580+
},
581+
array('bar' =>'baz'),
582+
array(
583+
'type' =>E_USER_DEPRECATED,
584+
'message' =>'The option "foo" is deprecated.',
585+
),
586+
);
587+
588+
yield'It deprecates allowed type and value' =>array(
589+
function (OptionsResolver$resolver) {
590+
$resolver
591+
->setDefault('foo',null)
592+
->setAllowedTypes('foo',array('null','string', \stdClass::class))
593+
->setDeprecated('foo',function ($value) {
594+
if ($valueinstanceof \stdClass) {
595+
returnsprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', \stdClass::class);
596+
}
597+
598+
return'';
599+
})
600+
;
601+
},
602+
array('foo' =>new \stdClass()),
603+
array(
604+
'type' =>E_USER_DEPRECATED,
605+
'message' =>'Passing an instance of "stdClass" to option "foo" is deprecated, pass its FQCN instead.',
606+
),
607+
);
608+
609+
yield'It ignores deprecation for missing option without default value' =>array(
610+
function (OptionsResolver$resolver) {
611+
$resolver
612+
->setDefined(array('foo','bar'))
613+
->setDeprecated('foo')
614+
;
615+
},
616+
array('bar' =>'baz'),
617+
null,
618+
);
619+
620+
yield'It ignores deprecation if closure returns an empty string' =>array(
621+
function (OptionsResolver$resolver) {
622+
$resolver
623+
->setDefault('foo',null)
624+
->setDeprecated('foo',function ($value) {
625+
return'';
626+
})
627+
;
628+
},
629+
array('foo' => Bar::class),
630+
null,
631+
);
632+
}
633+
453634
/**
454635
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
455636
*/

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp