Skip to content

Navigation Menu

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

Appearance settings

Commit 5aee060

Browse filesBrowse files
committed
Add setNested method
1 parent d4566b2 commit 5aee060
Copy full SHA for 5aee060

11 files changed

+882
-91
lines changed

‎UPGRADE-7.3.md

Copy file name to clipboardExpand all lines: UPGRADE-7.3.md
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@ FrameworkBundle
3939
because its default value will change in version 8.0
4040
* Deprecate the `--show-arguments` option of the `container:debug` command, as arguments are now always shown
4141

42+
OptionsResolver
43+
---------------
44+
45+
* Deprecate defining nested options via `setDefault()`, use `setNested()` instead
46+
47+
*Before*
48+
```php
49+
$resolver->setDefault('option', function (OptionsResolver $resolver) {
50+
// ...
51+
});
52+
```
53+
54+
*After*
55+
```php
56+
$resolver->setNested('option', function (OptionsResolver $resolver) {
57+
// ...
58+
});
59+
```
60+
4261
Serializer
4362
----------
4463

‎src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ protected function configureOptions(OptionsResolver $resolver): void
170170
$resolver->setAllowedTypes('debug', 'bool');
171171
$resolver->setDefault('referrals', false);
172172
$resolver->setAllowedTypes('referrals', 'bool');
173-
$resolver->setDefault('options', function (OptionsResolver $options, Options $parent) {
173+
$resolver->setNested('options', function (OptionsResolver $options, Options $parent) {
174174
$options->setDefined(array_map('strtolower', array_keys((new \ReflectionClass(ConnectionOptions::class))->getConstants())));
175175

176176
if (true === $parent['debug']) {

‎src/Symfony/Component/Ldap/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Ldap/composer.json
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818
"require": {
1919
"php": ">=8.2",
2020
"ext-ldap": "*",
21-
"symfony/options-resolver": "^6.4|^7.0"
21+
"symfony/options-resolver": "^7.3"
2222
},
2323
"require-dev": {
2424
"symfony/security-core": "^6.4|^7.0",
2525
"symfony/security-http": "^6.4|^7.0"
2626
},
2727
"conflict": {
28-
"symfony/options-resolver": "<6.4",
2928
"symfony/security-core": "<6.4"
3029
},
3130
"autoload": {

‎src/Symfony/Component/OptionsResolver/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/OptionsResolver/CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Support union type in `OptionResolver::setAllowedTypes()` method
8+
* Add `OptionsResolver::setNested()` and `OptionConfigurator::nested()` methods
9+
* Deprecate defining nested options via `setDefault()`, use `setNested()` instead
810

911
6.4
1012
---

‎src/Symfony/Component/OptionsResolver/Debug/OptionsResolverIntrospector.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/OptionsResolver/Debug/OptionsResolverIntrospector.php
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,14 @@ public function getDeprecation(string $option): array
101101
{
102102
return ($this->get)('deprecated', $option, \sprintf('No deprecation was set for the "%s" option.', $option));
103103
}
104+
105+
/**
106+
* @return \Closure[]
107+
*
108+
* @throws NoConfigurationException when no nested is configured
109+
*/
110+
public function getNested(string $option): array
111+
{
112+
return ($this->get)('nested', $option, \sprintf('No nested was set for the "%s" option.', $option));
113+
}
104114
}

‎src/Symfony/Component/OptionsResolver/OptionConfigurator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/OptionsResolver/OptionConfigurator.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,18 @@ public function ignoreUndefined(bool $ignore = true): static
143143

144144
return $this;
145145
}
146+
147+
/**
148+
* Defines nested options.
149+
*
150+
* @param \Closure(OptionsResolver $resolver, Options $parent): void $nested
151+
*
152+
* @return $this
153+
*/
154+
public function nested(\Closure $nested): static
155+
{
156+
$this->resolver->setNested($this->name, $nested);
157+
158+
return $this;
159+
}
146160
}

‎src/Symfony/Component/OptionsResolver/OptionsResolver.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/OptionsResolver/OptionsResolver.php
+79-41Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ class OptionsResolver implements Options
6464
*/
6565
private array $nested = [];
6666

67+
/**
68+
* BC layer. Remove in Symfony 8.0.
69+
*
70+
* @var array<string>
71+
*/
72+
private array $deprecatedNested = [];
73+
6774
/**
6875
* The names of required options.
6976
*/
@@ -178,20 +185,6 @@ class OptionsResolver implements Options
178185
* is spread across different locations of your code, such as base and
179186
* sub-classes.
180187
*
181-
* If you want to define nested options, you can pass a closure with the
182-
* following signature:
183-
*
184-
* $options->setDefault('database', function (OptionsResolver $resolver) {
185-
* $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']);
186-
* }
187-
*
188-
* To get access to the parent options, add a second argument to the closure's
189-
* signature:
190-
*
191-
* function (OptionsResolver $resolver, Options $parent) {
192-
* // 'default' === $parent['connection']
193-
* }
194-
*
195188
* @return $this
196189
*
197190
* @throws AccessException If called from a lazy option or normalizer
@@ -226,13 +219,22 @@ public function setDefault(string $option, mixed $value): static
226219
$this->lazy[$option][] = $value;
227220
$this->defined[$option] = true;
228221

229-
// Make sure the option is processed and is not nested anymore
230-
unset($this->resolved[$option], $this->nested[$option]);
222+
// Make sure the option is processed
223+
unset($this->resolved[$option]);
224+
225+
// BC layer. Remove in Symfony 8.0.
226+
if (isset($this->deprecatedNested[$option])) {
227+
unset($this->nested[$option]);
228+
}
231229

232230
return $this;
233231
}
234232

233+
// Remove in Symfony 8.0.
235234
if (isset($params[0]) && ($type = $params[0]->getType()) instanceof \ReflectionNamedType && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
235+
trigger_deprecation('symfony/options-resolver', '7.3', 'Defining nested options via "%s()" is deprecated and will be removed in Symfony 8.0, use "setNested()" method instead.', __METHOD__);
236+
$this->deprecatedNested[$option] = true;
237+
236238
// Store closure for later evaluation
237239
$this->nested[$option][] = $value;
238240
$this->defaults[$option] = [];
@@ -245,8 +247,13 @@ public function setDefault(string $option, mixed $value): static
245247
}
246248
}
247249

248-
// This option is not lazy nor nested anymore
249-
unset($this->lazy[$option], $this->nested[$option]);
250+
// This option is not lazy anymore
251+
unset($this->lazy[$option]);
252+
253+
// BC layer. Remove in Symfony 8.0.
254+
if (isset($this->deprecatedNested[$option])) {
255+
unset($this->nested[$option]);
256+
}
250257

251258
// Yet undefined options can be marked as resolved, because we only need
252259
// to resolve options with lazy closures, normalizers or validation
@@ -403,6 +410,37 @@ public function getDefinedOptions(): array
403410
return array_keys($this->defined);
404411
}
405412

413+
/**
414+
* Defines nested options.
415+
*
416+
* @param \Closure(self $resolver, Options $parent): void $nested
417+
*
418+
* @return $this
419+
*/
420+
public function setNested(string $option, \Closure $nested): static
421+
{
422+
if ($this->locked) {
423+
throw new AccessException('Nested options cannot be defined from a lazy option or normalizer.');
424+
}
425+
426+
$reflection = new \ReflectionFunction($nested);
427+
$params = $reflection->getParameters();
428+
429+
if (!isset($params[0]) || !($type = $params[0]->getType()) instanceof \ReflectionNamedType || self::class !== $type->getName() || (isset($params[1]) && (!($type = $params[1]->getType()) instanceof \ReflectionNamedType || Options::class !== $type->getName()))) {
430+
throw new InvalidArgumentException(\sprintf('Argument 2 passed to "%s()" must be a \Closure where the first argument is of type "%s" and the second argument is of type "%s".', __METHOD__, __CLASS__, Options::class));
431+
}
432+
433+
// Store closure for later evaluation
434+
$this->nested[$option][] = $nested;
435+
$this->defaults[$option] = [];
436+
$this->defined[$option] = true;
437+
438+
// Make sure the option is processed
439+
unset($this->resolved[$option]);
440+
441+
return $this;
442+
}
443+
406444
public function isNested(string $option): bool
407445
{
408446
return isset($this->nested[$option]);
@@ -947,6 +985,29 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
947985

948986
$value = $this->defaults[$option];
949987

988+
// Resolve the option if the default value is lazily evaluated
989+
if (isset($this->lazy[$option])) {
990+
// If the closure is already being called, we have a cyclic
991+
// dependency
992+
if (isset($this->calling[$option])) {
993+
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
994+
}
995+
996+
// The following section must be protected from cyclic
997+
// calls. Set $calling for the current $option to detect a cyclic
998+
// dependency
999+
// BEGIN
1000+
$this->calling[$option] = true;
1001+
try {
1002+
foreach ($this->lazy[$option] as $closure) {
1003+
$value = $closure($this, $value);
1004+
}
1005+
} finally {
1006+
unset($this->calling[$option]);
1007+
}
1008+
// END
1009+
}
1010+
9501011
// Resolve the option if it is a nested definition
9511012
if (isset($this->nested[$option])) {
9521013
// If the closure is already being called, we have a cyclic dependency
@@ -989,29 +1050,6 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
9891050
}
9901051
}
9911052

992-
// Resolve the option if the default value is lazily evaluated
993-
if (isset($this->lazy[$option])) {
994-
// If the closure is already being called, we have a cyclic
995-
// dependency
996-
if (isset($this->calling[$option])) {
997-
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
998-
}
999-
1000-
// The following section must be protected from cyclic
1001-
// calls. Set $calling for the current $option to detect a cyclic
1002-
// dependency
1003-
// BEGIN
1004-
$this->calling[$option] = true;
1005-
try {
1006-
foreach ($this->lazy[$option] as $closure) {
1007-
$value = $closure($this, $value);
1008-
}
1009-
} finally {
1010-
unset($this->calling[$option]);
1011-
}
1012-
// END
1013-
}
1014-
10151053
// Validate the type of the resolved option
10161054
if (isset($this->allowedTypes[$option])) {
10171055
$valid = true;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,13 @@ public function testGetDeprecationMessageThrowsOnNotDefinedOption()
263263
$debug = new OptionsResolverIntrospector($resolver);
264264
$debug->getDeprecation('foo');
265265
}
266+
267+
public function testGetClosureNested()
268+
{
269+
$resolver = new OptionsResolver();
270+
$resolver->setNested('foo', $closure = function (OptionsResolver $resolver) {});
271+
272+
$debug = new OptionsResolverIntrospector($resolver);
273+
$this->assertSame([$closure], $debug->getNested('foo'));
274+
}
266275
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.