Skip to content

Commit e4fe334

Browse files
committed
Allow scalar configuration in PHP Configuration
1 parent ba6de87 commit e4fe334

File tree

11 files changed

+311
-46
lines changed

11 files changed

+311
-46
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class ClassBuilder
3232
private array $use = [];
3333
private array $implements = [];
3434
private bool $allowExtraKeys = false;
35+
private ?string $valuesTypeHint = 'array';
3536

3637
public function __construct(string $namespace, string $name)
3738
{
@@ -169,4 +170,19 @@ public function shouldAllowExtraKeys(): bool
169170
{
170171
return $this->allowExtraKeys;
171172
}
173+
174+
public function setValuesTypeHint(?string $valuesTypeHint): void
175+
{
176+
$this->valuesTypeHint = $valuesTypeHint;
177+
}
178+
179+
public function getValuesTypeHint(): ?string
180+
{
181+
return $this->valuesTypeHint;
182+
}
183+
184+
public function shouldAllowScalaraValues(): bool
185+
{
186+
return 'array' !== $this->valuesTypeHint;
187+
}
172188
}

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

Lines changed: 136 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Component\Config\Builder;
1313

1414
use Symfony\Component\Config\Definition\ArrayNode;
15+
use Symfony\Component\Config\Definition\BaseNode;
1516
use Symfony\Component\Config\Definition\BooleanNode;
17+
use Symfony\Component\Config\Definition\Builder\ExprBuilder;
1618
use Symfony\Component\Config\Definition\ConfigurationInterface;
1719
use Symfony\Component\Config\Definition\EnumNode;
1820
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
@@ -90,6 +92,7 @@ private function writeClasses(): void
9092
$this->buildConstructor($class);
9193
$this->buildToArray($class);
9294
$this->buildSetExtraKey($class);
95+
$this->buildSetScalarValue($class);
9396

9497
file_put_contents($this->getFullPath($class), $class->build());
9598
}
@@ -127,23 +130,42 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
127130
{
128131
$childClass = new ClassBuilder($namespace, $node->getName());
129132
$childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys());
133+
$childClass->setValuesTypeHint($nodeType = $this->getParameterType($node));
130134
$class->addRequire($childClass);
131135
$this->classes[] = $childClass;
132136

133137
$property = $class->addProperty($node->getName(), $childClass->getFqcn());
138+
134139
$body = '
135-
public function NAME(array $value = []): CLASS
140+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
136141
{
137142
if (null === $this->PROPERTY) {
138143
$this->PROPERTY = new CLASS($value);
139144
} elseif ([] !== $value) {
140145
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
141146
}
142-
147+
';
148+
if ('array' === $nodeType) {
149+
$body .= '
143150
return $this->PROPERTY;
144151
}';
152+
} else {
153+
$body .= '
154+
if (\is_array($value)) {
155+
return $this->PROPERTY;
156+
}
157+
158+
return $this;
159+
}';
160+
}
161+
145162
$class->addUse(InvalidConfigurationException::class);
146-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
163+
$class->addMethod($node->getName(), $body, [
164+
'PROPERTY' => $property->getName(),
165+
'CLASS' => $childClass->getFqcn(),
166+
'RETURN_TYPEHINT' => 'array' === $nodeType ? $childClass->getFqcn() : 'self|'.$childClass->getFqcn(),
167+
'PARAM_TYPE' => $nodeType,
168+
]);
147169

148170
$this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
149171
}
@@ -174,39 +196,53 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
174196
$prototype = $node->getPrototype();
175197
$methodName = $name;
176198

177-
$parameterType = $this->getParameterType($prototype);
178-
if (null !== $parameterType || $prototype instanceof ScalarNode) {
199+
$nodeType = $this->getParameterType($node);
200+
$prototypeType = $this->getParameterType($prototype);
201+
202+
$isObject = $prototype instanceof ArrayNode && (!$prototype instanceof PrototypedArrayNode || !$prototype->getPrototype() instanceof ScalarNode);
203+
if (!$isObject) {
179204
$class->addUse(ParamConfigurator::class);
180205
$property = $class->addProperty($node->getName());
181206
if (null === $key = $node->getKeyAttribute()) {
182207
// This is an array of values; don't use singular name
208+
$nodeTypeWithoutArray = implode('|', array_filter(explode('|', $nodeType), fn ($type) => $type !== 'array'));
183209
$body = '
184210
/**
185-
* @param ParamConfigurator|list<ParamConfigurator|TYPE> $value
211+
* @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
186212
*
187213
* @return $this
188214
*/
189-
public function NAME(ParamConfigurator|array $value): static
215+
public function NAME(PARAM_TYPE $value): static
190216
{
191217
$this->PROPERTY = $value;
192218
193219
return $this;
194220
}';
195221

196-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType]);
222+
$class->addMethod($node->getName(), $body, [
223+
'PROPERTY' => $property->getName(),
224+
'PROTOTYPE_TYPE' => $prototypeType,
225+
'EXTRA_TYPE' => $nodeTypeWithoutArray ? '|'.$nodeTypeWithoutArray : '',
226+
'PARAM_TYPE' => $nodeType === 'mixed' ? 'mixed' : 'ParamConfigurator|'.$nodeType,
227+
]);
197228
} else {
198229
$body = '
199230
/**
200231
* @return $this
201232
*/
202-
public function NAME(string $VAR, TYPE $VALUE): static
233+
public function NAME(string $VAR, PARAM_TYPE $VALUE): static
203234
{
204235
$this->PROPERTY[$VAR] = $VALUE;
205236
206237
return $this;
207238
}';
208239

209-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : 'ParamConfigurator|'.$parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
240+
$class->addMethod($methodName, $body, [
241+
'PROPERTY' => $property->getName(),
242+
'VAR' => '' === $key ? 'key' : $key,
243+
'VALUE' => 'value' === $key ? 'data' : 'value',
244+
'PARAM_TYPE' => $prototypeType === 'mixed' ? 'mixed' : 'ParamConfigurator|'.$prototypeType,
245+
]);
210246
}
211247

212248
return;
@@ -216,20 +252,40 @@ public function NAME(string $VAR, TYPE $VALUE): static
216252
if ($prototype instanceof ArrayNode) {
217253
$childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys());
218254
}
255+
$childClass->setValuesTypeHint($nodeType);
256+
219257
$class->addRequire($childClass);
220258
$this->classes[] = $childClass;
221259
$property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]');
222260

223261
if (null === $key = $node->getKeyAttribute()) {
224262
$body = '
225-
public function NAME(array $value = []): CLASS
226-
{
263+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
264+
{';
265+
if ('array' === $nodeType) {
266+
$body .= '
227267
return $this->PROPERTY[] = new CLASS($value);
228268
}';
229-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
269+
} else {
270+
$body .= '
271+
$this->PROPERTY[] = $p = new CLASS($value);
272+
if (\is_array($value)) {
273+
return $p;
274+
}
275+
276+
return $this;
277+
}';
278+
}
279+
280+
$class->addMethod($methodName, $body, [
281+
'PROPERTY' => $property->getName(),
282+
'CLASS' => $childClass->getFqcn(),
283+
'RETURN_TYPEHINT' => 'array' === $nodeType ? $childClass->getFqcn() : 'self|'.$childClass->getFqcn(),
284+
'PARAM_TYPE' => $nodeType,
285+
]);
230286
} else {
231287
$body = '
232-
public function NAME(string $VAR, array $VALUE = []): CLASS
288+
public function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS
233289
{
234290
if (!isset($this->PROPERTY[$VAR])) {
235291
return $this->PROPERTY[$VAR] = new CLASS($value);
@@ -241,7 +297,13 @@ public function NAME(string $VAR, array $VALUE = []): CLASS
241297
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
242298
}';
243299
$class->addUse(InvalidConfigurationException::class);
244-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
300+
$class->addMethod($methodName, $body, [
301+
'PROPERTY' => $property->getName(),
302+
'CLASS' => $childClass->getFqcn(),
303+
'VAR' => '' === $key ? 'key' : $key,
304+
'VALUE' => 'value' === $key ? 'data' : 'value',
305+
'PARAM_TYPE' => $nodeType,
306+
]);
245307
}
246308

247309
$this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName());
@@ -267,35 +329,48 @@ public function NAME($value): static
267329
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
268330
}
269331

270-
private function getParameterType(NodeInterface $node): ?string
332+
private function getParameterType(NodeInterface $node): string
271333
{
334+
$paramTypes = [];
335+
if ($node instanceof BaseNode) {
336+
$types = $node->getNormalizedTypes();
337+
if (\in_array(ExprBuilder::TYPE_ANY, $types, true)) {
338+
return 'mixed';
339+
}
340+
341+
if (\in_array(ExprBuilder::TYPE_STRING, $types, true)) {
342+
$paramTypes[] = 'string';
343+
}
344+
if (\in_array(ExprBuilder::TYPE_NULL, $types, true)) {
345+
$paramTypes[] = 'null';
346+
}
347+
}
348+
272349
if ($node instanceof BooleanNode) {
273-
return 'bool';
350+
$paramTypes[] = 'bool';
274351
}
275352

276353
if ($node instanceof IntegerNode) {
277-
return 'int';
354+
$paramTypes[] = 'int';
278355
}
279356

280357
if ($node instanceof FloatNode) {
281-
return 'float';
358+
$paramTypes[] = 'float';
282359
}
283360

284361
if ($node instanceof EnumNode) {
285-
return '';
362+
$paramTypes[] = 'mixed';
286363
}
287364

288-
if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) {
289-
// This is just an array of variables
290-
return 'array';
365+
if ($node instanceof ArrayNode) {
366+
$paramTypes[] = 'array';
291367
}
292368

293369
if ($node instanceof VariableNode) {
294-
// mixed
295-
return '';
370+
$paramTypes[] = 'mixed';
296371
}
297372

298-
return null;
373+
return implode('|', $paramTypes);
299374
}
300375

301376
private function getComment(VariableNode $node): string
@@ -319,7 +394,7 @@ private function getComment(VariableNode $node): string
319394
}, $node->getValues())))."\n";
320395
} else {
321396
$parameterType = $this->getParameterType($node);
322-
if (null === $parameterType || '' === $parameterType) {
397+
if (null === $parameterType) {
323398
$parameterType = 'mixed';
324399
}
325400
$comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'."\n";
@@ -356,7 +431,15 @@ private function getSingularName(PrototypedArrayNode $node): string
356431

357432
private function buildToArray(ClassBuilder $class): void
358433
{
359-
$body = '$output = [];';
434+
$body = '';
435+
if ($class->shouldAllowScalaraValues()) {
436+
$body = 'if ($this->_value !== []) {
437+
return $this->_value;
438+
}
439+
440+
';
441+
}
442+
$body .= '$output = [];';
360443
foreach ($class->getProperties() as $p) {
361444
$code = '$this->PROPERTY';
362445
if (null !== $p->getType()) {
@@ -374,9 +457,10 @@ private function buildToArray(ClassBuilder $class): void
374457
}
375458

376459
$extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
460+
$nodeType = $class->getValuesTypeHint();
377461

378462
$class->addMethod('toArray', '
379-
public function NAME(): array
463+
public function NAME()'.($nodeType ? ': '.$nodeType : '').'
380464
{
381465
'.$body.'
382466
@@ -418,10 +502,21 @@ private function buildConstructor(ClassBuilder $class): void
418502
$class->addUse(InvalidConfigurationException::class);
419503
}
420504

505+
if ($class->shouldAllowScalaraValues()) {
506+
$body = '
507+
if (!\is_array($value)) {
508+
$this->_value = $value;
509+
510+
return;
511+
}
512+
$this->_value = [];
513+
'.$body;
514+
}
515+
516+
$nodeType = $class->getValuesTypeHint();
421517
$class->addMethod('__construct', '
422-
public function __construct(array $value = [])
423-
{
424-
'.$body.'
518+
public function __construct('.($nodeType).' $value = [])
519+
{'.$body.'
425520
}');
426521
}
427522

@@ -453,6 +548,15 @@ public function NAME(string $key, mixed $value): static
453548
}');
454549
}
455550

551+
private function buildSetScalarValue(ClassBuilder $class): void
552+
{
553+
if (!$class->shouldAllowScalaraValues()) {
554+
return;
555+
}
556+
557+
$class->addProperty('_value');
558+
}
559+
456560
private function getSubNamespace(ClassBuilder $rootClass): string
457561
{
458562
return sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6));

src/Symfony/Component/Config/Definition/BaseNode.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ abstract class BaseNode implements NodeInterface
3232
protected $name;
3333
protected $parent;
3434
protected $normalizationClosures = [];
35+
protected $normalizedTypes = [];
3536
protected $finalValidationClosures = [];
3637
protected $allowOverwrite = true;
3738
protected $required = false;
@@ -212,6 +213,24 @@ public function setNormalizationClosures(array $closures)
212213
$this->normalizationClosures = $closures;
213214
}
214215

216+
/**
217+
* Sets the list of type supported by normalization.
218+
* see ExprBuilder::TYPE_* constants.
219+
*/
220+
public function setNormalizedTypes(array $types)
221+
{
222+
$this->normalizedTypes = $types;
223+
}
224+
225+
/**
226+
* Gets the list of type supported by normalization.
227+
* see ExprBuilder::TYPE_* constants.
228+
*/
229+
public function getNormalizedTypes(): array
230+
{
231+
return $this->normalizedTypes;
232+
}
233+
215234
/**
216235
* Sets the closures used for final validation.
217236
*

src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ protected function createNode(): NodeInterface
420420

421421
if (null !== $this->normalization) {
422422
$node->setNormalizationClosures($this->normalization->before);
423+
$node->setNormalizedTypes($this->normalization->declaredTypes);
423424
$node->setXmlRemappings($this->normalization->remappings);
424425
}
425426

0 commit comments

Comments
 (0)