8000 Add setOptions method · symfony/symfony@b0bb9a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit b0bb9a1

Browse files
committed
Add setOptions method
1 parent 68944f5 commit b0bb9a1

11 files changed

+852
-99
lines changed

UPGRADE-7.3.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,25 @@ FrameworkBundle
9494
* Deprecate the `--show-arguments` option of the `container:debug` command, as arguments are now always shown
9595
* Deprecate the `framework.validation.cache` config option
9696

97+
OptionsResolver
98+
---------------
99+
100+
* Deprecate defining nested options via `setDefault()`, use `setOptions()` instead
101+
102+
*Before*
103+
```php
104+
$resolver->setDefault('option', function (OptionsResolver $resolver) {
105+
// ...
106+
});
107+
```
108+
109+
*After*
110+
```php
111+
$resolver->setOptions('option', function (OptionsResolver $resolver) {
112+
// ...
113+
});
114+
```
115+
97116
SecurityBundle
98117
--------------
99118

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

Lines 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->setOptions('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

Lines 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

Lines 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::setOptions()` and `OptionConfigurator::options()` methods
9+
* Deprecate defining nested options via `setDefault()`, use `setOptions()` instead
810

911
6.4
1012
---

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

Lines 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 option is configured
109+
*/
110+
public function getNestedOptions(string $option): array
111+
{
112+
return ($this->get)('nested', $option, \sprintf('No nested option was set for the "%s" option.', $option));
113+
}
104114
}

src/Symfony/Component/OptionsResolver/OptionConfigurator.php

Lines 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 options(\Closure $nested): static
155+
{
156+
$this->resolver->setOptions($this->name, $nested);
157+
158+
return $this;
159+
}
146160
}

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 66 additions & 42 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, true>
71+
*/
72+
private array $deprecatedNestedOptions = [];
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->deprecatedNestedOptions[$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 "setOptions()" method instead.', __METHOD__);
236+
$this->deprecatedNestedOptions[$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->deprecatedNestedOptions[$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,30 @@ 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 setOptions(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+
// Store closure for later evaluation
427+
$this->nested[$option][] = $nested;
428+
$this->defaults[$option] = [];
429+
$this->defined[$option] = true;
430+
431+
// Make sure the option is processed
432+
unset($this->resolved[$option]);
433+
434+
return $this;
435+
}
436+
406437
public function isNested(string $option): bool
407438
{
408439
return isset($this->nested[$option]);
@@ -947,6 +978,23 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
947978

948979
$value = $this->defaults[$option];
949980

981+
// Resolve the option if the default value is lazily evaluated
982+
if (isset($this->lazy[$option])) {
983+
// If the closure is already being called, we have a cyclic dependency
984+
if (isset($this->calling[$option])) {
985+
throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
986+
}
987+
988+
$this->calling[$option] = true;
989+
try {
990+
foreach ($this->lazy[$option] as $closure) {
991+
$value = $closure($this, $value);
992+
}
993+
} finally {
994+
unset($this->calling[$option]);
995+
}
996+
}
997+
950998
// Resolve the option if it is a nested definition
951999
if (isset($this->nested[$option])) {
9521000
// If the closure is already being called, we have a cyclic dependency
@@ -958,7 +1006,6 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
9581006
throw new InvalidOptionsException(\sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value)));
9591007
}
9601008

961-
// The following section must be protected from cyclic calls.
9621009
$this->calling[$option] = true;
9631010
try {
9641011
$resolver = new self();
@@ -989,29 +1036,6 @@ public function offsetGet(mixed $option, bool $triggerDeprecation = true): mixed
9891036
}
9901037
}
9911038

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-
10151039
// Validate the type of the resolved option
10161040
if (isset($this->allowedTypes[$option])) {
10171041
$valid = true;

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

Lines 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->setOptions('foo', $closure = function (OptionsResolver $resolver) {});
271+
272+
$debug = new OptionsResolverIntrospector($resolver);
273+
$this->assertSame([$closure], $debug->getNestedOptions('foo'));
274+
}
266275
}

0 commit comments

Comments
 (0)
0