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

Skip to content

Commit 003499d

Browse files
committed
feature #28738 [OptionsResolver] Passing Options argument to deprecation closure (yceruto)
This PR was squashed before being merged into the 4.2-dev branch (closes #28738). Discussion ---------- [OptionsResolver] Passing Options argument to deprecation closure | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #28721 (comment) | License | MIT | Doc PR | symfony/symfony-docs#10439 As spotted here #28721, we sometimes need more advanced cases, where the deprecation of the value depends on another option: ```php $resolver->setDeprecated('date_format', function (Options $options, $dateFormat) { if (null !== $options['date_format'] && 'single_text' === $options['widget']) { return sprintf('Using the "date_format" option of the %s when the "widget" option is set to "single_text" is deprecated since Symfony 4.2.', self::class); } return ''; }); ``` There is still a decision to make: > We're in time to change the arguments position (Options $options, $value) to be consistent with other closure signatures. WDYT? Commits ------- 2936051 [OptionsResolver] Passing Options argument to deprecation closure
2 parents 51f39b9 + 2936051 commit 003499d

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,12 @@ public function getDefinedOptions()
360360
* Instead of passing the message, you may also pass a closure with the
361361
* following signature:
362362
*
363-
* function ($value) {
363+
* function (Options $options, $value): string {
364364
* // ...
365365
* }
366366
*
367367
* The closure receives the value as argument and should return a string.
368-
* Returns an empty string to ignore the option deprecation.
368+
* Return an empty string to ignore the option deprecation.
369369
*
370370
* The closure is invoked when {@link resolve()} is called. The parameter
371371
* passed to the closure is the value of the option after validating it
@@ -860,8 +860,20 @@ public function offsetGet($option)
860860
if (isset($this->deprecated[$option])) {
861861
$deprecationMessage = $this->deprecated[$option];
862862

863-
if ($deprecationMessage instanceof \Closure && !\is_string($deprecationMessage = $deprecationMessage($value))) {
864-
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", returns an empty string to ignore.', \gettype($deprecationMessage)));
863+
if ($deprecationMessage instanceof \Closure) {
864+
// If the closure is already being called, we have a cyclic dependency
865+
if (isset($this->calling[$option])) {
866+
throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling))));
867+
}
868+
869+
$this->calling[$option] = true;
870+
try {
871+
if (!\is_string($deprecationMessage = $deprecationMessage($this, $value))) {
872+
throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', \gettype($deprecationMessage)));
873+
}
874+
} finally {
875+
unset($this->calling[$option]);
876+
}
865877
}
866878

867879
if ('' !== $deprecationMessage) {

src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public function testGetClosureDeprecationMessage()
215215
{
216216
$resolver = new OptionsResolver();
217217
$resolver->setDefined('foo');
218-
$resolver->setDeprecated('foo', $closure = function ($value) {});
218+
$resolver->setDeprecated('foo', $closure = function (Options $options, $value) {});
219219

220220
$debug = new OptionsResolverIntrospector($resolver);
221221
$this->assertSame($closure, $debug->getDeprecationMessage('foo'));

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,19 +486,38 @@ public function testSetDeprecatedFailsIfInvalidDeprecationMessageType()
486486

487487
/**
488488
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidArgumentException
489-
* @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", returns an empty string to ignore.
489+
* @expectedExceptionMessage Invalid type for deprecation message, expected string but got "boolean", return an empty string to ignore.
490490
*/
491491
public function testLazyDeprecationFailsIfInvalidDeprecationMessageType()
492492
{
493493
$this->resolver
494494
->setDefault('foo', true)
495-
->setDeprecated('foo', function ($value) {
495+
->setDeprecated('foo', function (Options $options, $value) {
496496
return false;
497497
})
498498
;
499499
$this->resolver->resolve();
500500
}
501501

502+
/**
503+
* @expectedException \Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
504+
* @expectedExceptionMessage The options "foo", "bar" have a cyclic dependency.
505+
*/
506+
public function testFailsIfCyclicDependencyBetweenDeprecation()
507+
{
508+
$this->resolver
509+
->setDefault('foo', null)
510+
->setDefault('bar', null)
511+
->setDeprecated('foo', function (Options $options, $value) {
512+
$options['bar'];
513+
})
514+
->setDeprecated('bar', function (Options $options, $value) {
515+
$options['foo'];
516+
})
517+
;
518+
$this->resolver->resolve();
519+
}
520+
502521
public function testIsDeprecated()
503522
{
504523
$this->resolver
@@ -590,7 +609,7 @@ function (OptionsResolver $resolver) {
590609
$resolver
591610
->setDefault('foo', null)
592611
->setAllowedTypes('foo', array('null', 'string', \stdClass::class))
593-
->setDeprecated('foo', function ($value) {
612+
->setDeprecated('foo', function (Options $options, $value) {
594613
if ($value instanceof \stdClass) {
595614
return sprintf('Passing an instance of "%s" to option "foo" is deprecated, pass its FQCN instead.', \stdClass::class);
596615
}
@@ -621,14 +640,35 @@ function (OptionsResolver $resolver) {
621640
function (OptionsResolver $resolver) {
622641
$resolver
623642
->setDefault('foo', null)
624-
->setDeprecated('foo', function ($value) {
643+
->setDeprecated('foo', function (Options $options, $value) {
625644
return '';
626645
})
627646
;
628647
},
629648
array('foo' => Bar::class),
630649
null,
631650
);
651+
652+
yield 'It deprecates value depending on other option value' => array(
653+
function (OptionsResolver $resolver) {
654+
$resolver
655+
->setDefault('widget', null)
656+
->setDefault('date_format', null)
657+
->setDeprecated('date_format', function (Options $options, $dateFormat) {
658+
if (null !== $dateFormat && 'single_text' === $options['widget']) {
659+
return 'Using the "date_format" option when the "widget" option is set to "single_text" is deprecated.';
660+
}
661+
662+
return '';
663+
})
664+
;
665+
},
666+
array('widget' => 'single_text', 'date_format' => 2),
667+
array(
668+
'type' => E_USER_DEPRECATED,
669+
'message' => 'Using the "date_format" option when the "widget" option is set to "single_text" is deprecated.',
670+
),
671+
);
632672
}
633673

634674
/**

0 commit comments

Comments
 (0)