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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy