Skip to content

Commit 332b4ac

Browse files
[Config][DependencyInjection] Deprecate the fluent PHP format for semantic configuration
1 parent bcb1ba5 commit 332b4ac

38 files changed

+541
-145
lines changed

UPGRADE-7.4.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Config
2323

2424
* Deprecate accessing the internal scope of the loader in PHP config files, use only its public API instead
2525
* Deprecate setting a default value to a node that is required, and vice versa
26+
* Deprecate generating fluent methods in config builders
2627

2728
Console
2829
-------
@@ -40,6 +41,15 @@ DependencyInjection
4041
* Deprecate `ExtensionInterface::getXsdValidationBasePath()` and `getNamespace()`;
4142
bundles that need to support older versions of Symfony can keep the methods
4243
but need to add the `@deprecated` annotation on them
44+
* Deprecate the fluent PHP format for semantic configuration, instantiate builders inline with the config array as argument and return them instead:
45+
```diff
46+
-return function (AcmeConfig $config) {
47+
- $config->color('red');
48+
-}
49+
+return new AcmeConfig([
50+
+ 'color' => 'red',
51+
+]);
52+
```
4353

4454
DoctrineBridge
4555
--------------

src/Symfony/Component/Config/Builder/ClassBuilder.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public function __construct(
3838
private string $namespace,
3939
string $name,
4040
private NodeInterface $node,
41+
public readonly bool $isRoot = false,
4142
) {
4243
$this->name = ucfirst($this->camelCase($name)).'Config';
4344
}
@@ -73,7 +74,7 @@ public function build(): string
7374
$use .= \sprintf('use %s;', $statement)."\n";
7475
}
7576

76-
$implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements);
77+
$implements = $this->implements ? 'implements '.implode(', ', $this->implements) : '';
7778
$body = '';
7879
foreach ($this->properties as $property) {
7980
$body .= ' '.$property->getContent()."\n";

src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function build(ConfigurationInterface $configuration): \Closure
5151
$this->classes = [];
5252

5353
$rootNode = $configuration->getConfigTreeBuilder()->buildTree();
54-
$rootClass = new ClassBuilder('Symfony\\Config', $rootNode->getName(), $rootNode);
54+
$rootClass = new ClassBuilder('Symfony\\Config', $rootNode->getName(), $rootNode, true);
5555

5656
$path = $this->getFullPath($rootClass);
5757
if (!is_file($path)) {
@@ -68,7 +68,7 @@ public function NAME(): string
6868
$this->writeClasses();
6969
}
7070

71-
return function () use ($path, $rootClass) {
71+
return static function () use ($path, $rootClass) {
7272
require_once $path;
7373
$className = $rootClass->getFqcn();
7474

@@ -94,6 +94,9 @@ private function writeClasses(): void
9494
if ($class->getProperties()) {
9595
$class->addProperty('_usedProperties', null, '[]');
9696
}
97+
if ($class->isRoot) {
98+
$class->addProperty('_hasDeprecatedCalls', null, 'false');
99+
}
97100
$this->buildSetExtraKey($class);
98101

99102
file_put_contents($this->getFullPath($class), $class->build());
@@ -134,10 +137,13 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
134137
if ($acceptScalar) {
135138
$comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
136139
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
137-
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
140+
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n", $childClass->getFqcn());
141+
}
142+
if ($class->isRoot) {
143+
$comment .= " * @deprecated since Symfony 7.4\n";
138144
}
139145
if ('' !== $comment) {
140-
$comment = "/**\n$comment*/\n";
146+
$comment = "/**\n$comment */\n";
141147
}
142148

143149
$property = $class->addProperty(
@@ -146,7 +152,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
146152
);
147153
$body = $acceptScalar ? '
148154
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
149-
{
155+
{DEPRECATED_BODY
150156
if (!\is_array($value)) {
151157
$this->_usedProperties[\'PROPERTY\'] = true;
152158
$this->PROPERTY = $value;
@@ -164,7 +170,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
164170
return $this->PROPERTY;
165171
}' : '
166172
COMMENTpublic function NAME(array $value = []): CLASS
167-
{
173+
{DEPRECATED_BODY
168174
if (null === $this->PROPERTY) {
169175
$this->_usedProperties[\'PROPERTY\'] = true;
170176
$this->PROPERTY = new CLASS($value);
@@ -176,6 +182,7 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
176182
}';
177183
$class->addUse(InvalidConfigurationException::class);
178184
$class->addMethod($node->getName(), $body, [
185+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
179186
'COMMENT' => $comment,
180187
'PROPERTY' => $property->getName(),
181188
'CLASS' => $childClass->getFqcn(),
@@ -195,15 +202,17 @@ private function handleVariableNode(VariableNode $node, ClassBuilder $class): vo
195202
/**
196203
COMMENT *
197204
* @return $this
198-
*/
205+
*DEPRECATED_ANNOTATION/
199206
public function NAME(mixed $valueDEFAULT): static
200-
{
207+
{DEPRECATED_BODY
201208
$this->_usedProperties[\'PROPERTY\'] = true;
202209
$this->PROPERTY = $value;
203210
204211
return $this;
205212
}';
206213
$class->addMethod($node->getName(), $body, [
214+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
215+
'DEPRECATED_ANNOTATION' => $class->isRoot ? " @deprecated since Symfony 7.4\n *" : '',
207216
'PROPERTY' => $property->getName(),
208217
'COMMENT' => $comment,
209218
'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '',
@@ -232,16 +241,18 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
232241
* @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
233242
*
234243
* @return $this
235-
*/
244+
*DEPRECATED_ANNOTATION/
236245
public function NAME(PARAM_TYPE $value): static
237-
{
246+
{DEPRECATED_BODY
238247
$this->_usedProperties[\'PROPERTY\'] = true;
239248
$this->PROPERTY = $value;
240249
241250
return $this;
242251
}';
243252

244253
$class->addMethod($node->getName(), $body, [
254+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
255+
'DEPRECATED_ANNOTATION' => $class->isRoot ? " @deprecated since Symfony 7.4\n *" : '',
245256
'PROPERTY' => $property->getName(),
246257
'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes),
247258
'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '',
@@ -251,16 +262,18 @@ public function NAME(PARAM_TYPE $value): static
251262
$body = '
252263
/**
253264
* @return $this
254-
*/
265+
*DEPRECATED_ANNOTATION/
255266
public function NAME(string $VAR, TYPE $VALUE): static
256-
{
267+
{DEPRECATED_BODY
257268
$this->_usedProperties[\'PROPERTY\'] = true;
258269
$this->PROPERTY[$VAR] = $VALUE;
259270
260271
return $this;
261272
}';
262273

263274
$class->addMethod($methodName, $body, [
275+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
276+
'DEPRECATED_ANNOTATION' => $class->isRoot ? " @deprecated since Symfony 7.4\n *" : '',
264277
'PROPERTY' => $property->getName(),
265278
'TYPE' => ['mixed'] !== $prototypeParameterTypes ? 'ParamConfigurator|'.implode('|', $prototypeParameterTypes) : 'mixed',
266279
'VAR' => '' === $key ? 'key' : $key,
@@ -290,16 +303,19 @@ public function NAME(string $VAR, TYPE $VALUE): static
290303
if ($acceptScalar) {
291304
$comment = \sprintf(" * @template TValue of %s\n * @param TValue \$value\n%s", $paramType, $comment);
292305
$comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
293-
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
306+
$comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n", $childClass->getFqcn());
307+
}
308+
if ($class->isRoot) {
309+
$comment .= " * @deprecated since Symfony 7.4\n";
294310
}
295311
if ('' !== $comment) {
296-
$comment = "/**\n$comment*/\n";
312+
$comment = "/**\n$comment */\n";
297313
}
298314

299315
if ($noKey) {
300316
$body = $acceptScalar ? '
301317
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
302-
{
318+
{DEPRECATED_BODY
303319
$this->_usedProperties[\'PROPERTY\'] = true;
304320
if (!\is_array($value)) {
305321
$this->PROPERTY[] = $value;
@@ -310,12 +326,13 @@ public function NAME(string $VAR, TYPE $VALUE): static
310326
return $this->PROPERTY[] = new CLASS($value);
311327
}' : '
312328
COMMENTpublic function NAME(array $value = []): CLASS
313-
{
329+
{DEPRECATED_BODY
314330
$this->_usedProperties[\'PROPERTY\'] = true;
315331
316332
return $this->PROPERTY[] = new CLASS($value);
317333
}';
318334
$class->addMethod($methodName, $body, [
335+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
319336
'COMMENT' => $comment,
320337
'PROPERTY' => $property->getName(),
321338
'CLASS' => $childClass->getFqcn(),
@@ -324,7 +341,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
324341
} else {
325342
$body = $acceptScalar ? '
326343
COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static
327-
{
344+
{DEPRECATED_BODY
328345
if (!\is_array($VALUE)) {
329346
$this->_usedProperties[\'PROPERTY\'] = true;
330347
$this->PROPERTY[$VAR] = $VALUE;
@@ -342,7 +359,7 @@ public function NAME(string $VAR, TYPE $VALUE): static
342359
return $this->PROPERTY[$VAR];
343360
}' : '
344361
COMMENTpublic function NAME(string $VAR, array $VALUE = []): CLASS
345-
{
362+
{DEPRECATED_BODY
346363
if (!isset($this->PROPERTY[$VAR])) {
347364
$this->_usedProperties[\'PROPERTY\'] = true;
348365
$this->PROPERTY[$VAR] = new CLASS($VALUE);
@@ -354,7 +371,9 @@ public function NAME(string $VAR, TYPE $VALUE): static
354371
}';
355372
$class->addUse(InvalidConfigurationException::class);
356373
$class->addMethod($methodName, str_replace('$value', '$VAR', $body), [
357-
'COMMENT' => $comment, 'PROPERTY' => $property->getName(),
374+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
375+
'COMMENT' => $comment,
376+
'PROPERTY' => $property->getName(),
358377
'CLASS' => $childClass->getFqcn(),
359378
'VAR' => '' === $key ? 'key' : $key,
360379
'VALUE' => 'value' === $key ? 'data' : 'value',
@@ -374,16 +393,21 @@ private function handleScalarNode(ScalarNode $node, ClassBuilder $class): void
374393
$body = '
375394
/**
376395
COMMENT * @return $this
377-
*/
396+
*DEPRECATED_ANNOTATION/
378397
public function NAME($value): static
379-
{
398+
{DEPRECATED_BODY
380399
$this->_usedProperties[\'PROPERTY\'] = true;
381400
$this->PROPERTY = $value;
382401
383402
return $this;
384403
}';
385404

386-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
405+
$class->addMethod($node->getName(), $body, [
406+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
407+
'DEPRECATED_ANNOTATION' => $class->isRoot ? " @deprecated since Symfony 7.4\n *" : '',
408+
'PROPERTY' => $property->getName(),
409+
'COMMENT' => $comment,
410+
]);
387411
}
388412

389413
private function getParameterTypes(NodeInterface $node): array
@@ -509,6 +533,13 @@ private function buildToArray(ClassBuilder $class): void
509533

510534
$extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
511535

536+
if ($class->isRoot) {
537+
$body .= "
538+
if (\$this->_hasDeprecatedCalls) {
539+
trigger_deprecation('symfony/config', '7.4', 'Calling any fluent method on \"%s\" is deprecated; pass the configuration to the constructor instead.', \$this::class);
540+
}";
541+
}
542+
512543
$class->addMethod('toArray', '
513544
public function NAME(): array
514545
{
@@ -583,13 +614,16 @@ private function buildSetExtraKey(ClassBuilder $class): void
583614
* @param ParamConfigurator|mixed $value
584615
*
585616
* @return $this
586-
*/
617+
*DEPRECATED_ANNOTATION/
587618
public function NAME(string $key, mixed $value): static
588-
{
619+
{DEPRECATED_BODY
589620
$this->_extraKeys[$key] = $value;
590621
591622
return $this;
592-
}');
623+
}', [
624+
'DEPRECATED_BODY' => $class->isRoot ? "\n \$this->_hasDeprecatedCalls = true;" : '',
625+
'DEPRECATED_ANNOTATION' => $class->isRoot ? " @deprecated since Symfony 7.4\n *" : '',
626+
]);
593627
}
594628

595629
private function getSubNamespace(ClassBuilder $rootClass): string

src/Symfony/Component/Config/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Add array-shapes to generated config builders
1212
* Deprecate accessing the internal scope of the loader in PHP config files, use only its public API instead
1313
* Deprecate setting a default value to a node that is required, and vice versa
14+
* Deprecate generating fluent methods in config builders
1415

1516
7.3
1617
---

src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList.config.php

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,35 @@
1111

1212
use Symfony\Config\AddToListConfig;
1313

14-
return static function (AddToListConfig $config) {
15-
$config->translator()->fallbacks(['sv', 'fr', 'es']);
16-
$config->translator()->source('\\Acme\\Foo', 'yellow');
17-
$config->translator()->source('\\Acme\\Bar', 'green');
18-
19-
$config->messenger([
14+
return new AddToListConfig([
15+
'translator' => [
16+
'fallbacks' => ['sv', 'fr', 'es'],
17+
'sources' => [
18+
'\\Acme\\Foo' => 'yellow',
19+
'\\Acme\\Bar' => 'green',
20+
],
21+
],
22+
'messenger' => [
2023
'routing' => [
2124
'Foo\\MyArrayMessage' => [
2225
'senders' => ['workqueue'],
2326
],
27+
'Foo\\Message' => [
28+
'senders' => ['workqueue'],
29+
],
30+
'Foo\\DoubleMessage' => [
31+
'senders' => ['sync', 'workqueue'],
32+
],
2433
],
25-
]);
26-
$config->messenger()
27-
->routing('Foo\\Message')->senders(['workqueue']);
28-
$config->messenger()
29-
->routing('Foo\\DoubleMessage')->senders(['sync', 'workqueue']);
30-
31-
$config->messenger()->receiving()
32-
->color('blue')
33-
->priority(10);
34-
$config->messenger()->receiving()
35-
->color('red')
36-
->priority(5);
37-
};
34+
'receiving' => [
35+
[
36+
'color' => 'blue',
37+
'priority' => 10,
38+
],
39+
[
40+
'color' => 'red',
41+
'priority' => 5,
42+
],
43+
],
44+
],
45+
]);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\Config\AddToListConfig;
13+
14+
return static function (AddToListConfig $config) {
15+
$config->translator()->fallbacks(['sv', 'fr', 'es']);
16+
$config->translator()->source('\\Acme\\Foo', 'yellow');
17+
$config->translator()->source('\\Acme\\Bar', 'green');
18+
19+
$config->messenger([
20+
'routing' => [
21+
'Foo\\MyArrayMessage' => [
22+
'senders' => ['workqueue'],
23+
],
24+
],
25+
]);
26+
$config->messenger()
27+
->routing('Foo\\Message')->senders(['workqueue']);
28+
$config->messenger()
29+
->routing('Foo\\DoubleMessage')->senders(['sync', 'workqueue']);
30+
31+
$config->messenger()->receiving()
32+
->color('blue')
33+
->priority(10);
34+
$config->messenger()->receiving()
35+
->color('red')
36+
->priority(5);
37+
};

0 commit comments

Comments
 (0)