Skip to content

Commit 56c6dc1

Browse files
bug #46328 [Config] Allow scalar configuration in PHP Configuration (jderusse, HypeMC)
This PR was merged into the 5.4 branch. Discussion ---------- [Config] Allow scalar configuration in PHP Configuration | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix symfony/monolog-bundle#417 (comment) | License | MIT | Doc PR | - Fixes passing scalar values to array nodes that have a `beforeNormalization` hook, eg: ```php return static function (MonologConfig $config): void { $config->handler('console') // ... ->processPsr3Messages() ->enabled(true) ; }; ``` can be shortened thanks to a [`beforeNormalization` hook](https://github.com/symfony/monolog-bundle/blob/a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d/DependencyInjection/Configuration.php#L453): ```php return static function (MonologConfig $config): void { $config->handler('console') // ... ->processPsr3Messages(true) ; }; ``` I've used some of the code from #44166 by `@jderusse`. Since his PR is a feature it can't go on 5.4, but it still helped a lot. Commits ------- 2d81a3a [Config] Allow scalar configuration in PHP Configuration 1c176e1 [Config] Allow scalar configuration in PHP Configuration
2 parents 1ec8c1e + 2d81a3a commit 56c6dc1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1310
-254
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function build(): string
6868
}
6969
$require .= sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n";
7070
}
71-
$use = '';
71+
$use = $require ? "\n" : '';
7272
foreach (array_keys($this->use) as $statement) {
7373
$use .= sprintf('use %s;', $statement)."\n";
7474
}
@@ -81,17 +81,15 @@ public function build(): string
8181
foreach ($this->methods as $method) {
8282
$lines = explode("\n", $method->getContent());
8383
foreach ($lines as $line) {
84-
$body .= ' '.$line."\n";
84+
$body .= ($line ? ' '.$line : '')."\n";
8585
}
8686
}
8787

8888
$content = strtr('<?php
8989
9090
namespace NAMESPACE;
9191
92-
REQUIRE
93-
USE
94-
92+
REQUIREUSE
9593
/**
9694
* This class is automatically generated to help in creating a config.
9795
*/

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

Lines changed: 110 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,39 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
136136
$class->addRequire($childClass);
137137
$this->classes[] = $childClass;
138138

139-
$property = $class->addProperty($node->getName(), $childClass->getFqcn());
140-
$body = '
139+
$hasNormalizationClosures = $this->hasNormalizationClosures($node);
140+
$property = $class->addProperty(
141+
$node->getName(),
142+
$this->getType($childClass->getFqcn(), $hasNormalizationClosures)
143+
);
144+
$body = $hasNormalizationClosures ? '
145+
/**
146+
* @return CLASS|$this
147+
*/
148+
public function NAME($value = [])
149+
{
150+
if (!\is_array($value)) {
151+
$this->_usedProperties[\'PROPERTY\'] = true;
152+
$this->PROPERTY = $value;
153+
154+
return $this;
155+
}
156+
157+
if (!$this->PROPERTY instanceof CLASS) {
158+
$this->_usedProperties[\'PROPERTY\'] = true;
159+
$this->PROPERTY = new CLASS($value);
160+
} elseif (0 < \func_num_args()) {
161+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
162+
}
163+
164+
return $this->PROPERTY;
165+
}' : '
141166
public function NAME(array $value = []): CLASS
142167
{
143168
if (null === $this->PROPERTY) {
144169
$this->_usedProperties[\'PROPERTY\'] = true;
145170
$this->PROPERTY = new CLASS($value);
146-
} elseif ([] !== $value) {
171+
} elseif (0 < \func_num_args()) {
147172
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
148173
}
149174
@@ -227,10 +252,29 @@ public function NAME(string $VAR, $VALUE): self
227252
}
228253
$class->addRequire($childClass);
229254
$this->classes[] = $childClass;
230-
$property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]');
255+
256+
$hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype);
257+
$property = $class->addProperty(
258+
$node->getName(),
259+
$this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures)
260+
);
231261

232262
if (null === $key = $node->getKeyAttribute()) {
233-
$body = '
263+
$body = $hasNormalizationClosures ? '
264+
/**
265+
* @return CLASS|$this
266+
*/
267+
public function NAME($value = [])
268+
{
269+
$this->_usedProperties[\'PROPERTY\'] = true;
270+
if (!\is_array($value)) {
271+
$this->PROPERTY[] = $value;
272+
273+
return $this;
274+
}
275+
276+
return $this->PROPERTY[] = new CLASS($value);
277+
}' : '
234278
public function NAME(array $value = []): CLASS
235279
{
236280
$this->_usedProperties[\'PROPERTY\'] = true;
@@ -239,19 +283,38 @@ public function NAME(array $value = []): CLASS
239283
}';
240284
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
241285
} else {
242-
$body = '
243-
public function NAME(string $VAR, array $VALUE = []): CLASS
286+
$body = $hasNormalizationClosures ? '
287+
/**
288+
* @return CLASS|$this
289+
*/
290+
public function NAME(string $VAR, $VALUE = [])
244291
{
245-
if (!isset($this->PROPERTY[$VAR])) {
292+
if (!\is_array($VALUE)) {
246293
$this->_usedProperties[\'PROPERTY\'] = true;
294+
$this->PROPERTY[$VAR] = $VALUE;
247295
248-
return $this->PROPERTY[$VAR] = new CLASS($VALUE);
296+
return $this;
249297
}
250-
if ([] === $VALUE) {
251-
return $this->PROPERTY[$VAR];
298+
299+
if (!isset($this->PROPERTY[$VAR]) || !$this->PROPERTY[$VAR] instanceof CLASS) {
300+
$this->_usedProperties[\'PROPERTY\'] = true;
301+
$this->PROPERTY[$VAR] = new CLASS($VALUE);
302+
} elseif (1 < \func_num_args()) {
303+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
252304
}
253305
254-
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
306+
return $this->PROPERTY[$VAR];
307+
}' : '
308+
public function NAME(string $VAR, array $VALUE = []): CLASS
309+
{
310+
if (!isset($this->PROPERTY[$VAR])) {
311+
$this->_usedProperties[\'PROPERTY\'] = true;
312+
$this->PROPERTY[$VAR] = new CLASS($VALUE);
313+
} elseif (1 < \func_num_args()) {
314+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
315+
}
316+
317+
return $this->PROPERTY[$VAR];
255318
}';
256319
$class->addUse(InvalidConfigurationException::class);
257320
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
@@ -375,16 +438,22 @@ private function buildToArray(ClassBuilder $class): void
375438
$code = '$this->PROPERTY';
376439
if (null !== $p->getType()) {
377440
if ($p->isArray()) {
378-
$code = 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)';
441+
$code = $p->areScalarsAllowed()
442+
? 'array_map(function ($v) { return $v instanceof CLASS ? $v->toArray() : $v; }, $this->PROPERTY)'
443+
: 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)'
444+
;
379445
} else {
380-
$code = '$this->PROPERTY->toArray()';
446+
$code = $p->areScalarsAllowed()
447+
? '$this->PROPERTY instanceof CLASS ? $this->PROPERTY->toArray() : $this->PROPERTY'
448+
: '$this->PROPERTY->toArray()'
449+
;
381450
}
382451
}
383452

384453
$body .= strtr('
385454
if (isset($this->_usedProperties[\'PROPERTY\'])) {
386455
$output[\'ORG_NAME\'] = '.$code.';
387-
}', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
456+
}', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName(), 'CLASS' => $p->getType()]);
388457
}
389458

390459
$extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
@@ -405,9 +474,15 @@ private function buildConstructor(ClassBuilder $class): void
405474
$code = '$value[\'ORG_NAME\']';
406475
if (null !== $p->getType()) {
407476
if ($p->isArray()) {
408-
$code = 'array_map(function ($v) { return new '.$p->getType().'($v); }, $value[\'ORG_NAME\'])';
477+
$code = $p->areScalarsAllowed()
478+
? 'array_map(function ($v) { return \is_array($v) ? new '.$p->getType().'($v) : $v; }, $value[\'ORG_NAME\'])'
479+
: 'array_map(function ($v) { return new '.$p->getType().'($v); }, $value[\'ORG_NAME\'])'
480+
;
409481
} else {
410-
$code = 'new '.$p->getType().'($value[\'ORG_NAME\'])';
482+
$code = $p->areScalarsAllowed()
483+
? '\is_array($value[\'ORG_NAME\']) ? new '.$p->getType().'($value[\'ORG_NAME\']) : $value[\'ORG_NAME\']'
484+
: 'new '.$p->getType().'($value[\'ORG_NAME\'])'
485+
;
411486
}
412487
}
413488

@@ -435,8 +510,7 @@ private function buildConstructor(ClassBuilder $class): void
435510

436511
$class->addMethod('__construct', '
437512
public function __construct(array $value = [])
438-
{
439-
'.$body.'
513+
{'.$body.'
440514
}');
441515
}
442516

@@ -467,4 +541,21 @@ private function getSubNamespace(ClassBuilder $rootClass): string
467541
{
468542
return sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6));
469543
}
544+
545+
private function hasNormalizationClosures(NodeInterface $node): bool
546+
{
547+
try {
548+
$r = new \ReflectionProperty($node, 'normalizationClosures');
549+
} catch (\ReflectionException $e) {
550+
return false;
551+
}
552+
$r->setAccessible(true);
553+
554+
return [] !== $r->getValue($node);
555+
}
556+
557+
private function getType(string $classType, bool $hasNormalizationClosures): string
558+
{
559+
return $classType.($hasNormalizationClosures ? '|scalar' : '');
560+
}
470561
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Property
2323
private $name;
2424
private $originalName;
2525
private $array = false;
26+
private $scalarsAllowed = false;
2627
private $type = null;
2728
private $content;
2829

@@ -47,6 +48,11 @@ public function setType(string $type): void
4748
$this->array = false;
4849
$this->type = $type;
4950

51+
if ('|scalar' === substr($type, -7)) {
52+
$this->scalarsAllowed = true;
53+
$this->type = $type = substr($type, 0, -7);
54+
}
55+
5056
if ('[]' === substr($type, -2)) {
5157
$this->array = true;
5258
$this->type = substr($type, 0, -2);
@@ -72,4 +78,9 @@ public function isArray(): bool
7278
{
7379
return $this->array;
7480
}
81+
82+
public function areScalarsAllowed(): bool
83+
{
84+
return $this->scalarsAllowed;
85+
}
7586
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
use Symfony\Config\AddToListConfig;
413

514
return static function (AddToListConfig $config) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
return [
413
'translator' => [
514
'fallbacks' => ['sv', 'fr', 'es'],

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Symfony\Component\Config\Tests\Builder\Fixtures;
413

514
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

src/Symfony/Component/Config/Tests/Builder/Fixtures/AddToList/Symfony/Config/AddToList/Messenger/ReceivingConfig.php

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
namespace Symfony\Config\AddToList\Messenger;
44

5-
65
use Symfony\Component\Config\Loader\ParamConfigurator;
76
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
87

9-
108
/**
119
* This class is automatically generated to help in creating a config.
1210
*/
@@ -15,7 +13,7 @@ class ReceivingConfig
1513
private $priority;
1614
private $color;
1715
private $_usedProperties = [];
18-
16+
1917
/**
2018
* @default null
2119
* @param ParamConfigurator|int $value
@@ -25,10 +23,10 @@ public function priority($value): self
2523
{
2624
$this->_usedProperties['priority'] = true;
2725
$this->priority = $value;
28-
26+
2927
return $this;
3028
}
31-
29+
3230
/**
3331
* @default null
3432
* @param ParamConfigurator|mixed $value
@@ -38,30 +36,29 @@ public function color($value): self
3836
{
3937
$this->_usedProperties['color'] = true;
4038
$this->color = $value;
41-
39+
4240
return $this;
4341
}
44-
42+
4543
public function __construct(array $value = [])
4644
{
47-
4845
if (array_key_exists('priority', $value)) {
4946
$this->_usedProperties['priority'] = true;
5047
$this->priority = $value['priority'];
5148
unset($value['priority']);
5249
}
53-
50+
5451
if (array_key_exists('color', $value)) {
5552
$this->_usedProperties['color'] = true;
5653
$this->color = $value['color'];
5754
unset($value['color']);
5855
}
59-
56+
6057
if ([] !== $value) {
6158
throw new InvalidConfigurationException(sprintf('The following keys are not supported by "%s": ', __CLASS__).implode(', ', array_keys($value)));
6259
}
6360
}
64-
61+
6562
public function toArray(): array
6663
{
6764
$output = [];
@@ -71,7 +68,7 @@ public function toArray(): array
7168
if (isset($this->_usedProperties['color'])) {
7269
$output['color'] = $this->color;
7370
}
74-
71+
7572
return $output;
7673
}
7774

0 commit comments

Comments
 (0)