Skip to content

Commit 962834b

Browse files
committed
Allow scalar configuration in PHP Configuration
1 parent 3fb7af0 commit 962834b

File tree

29 files changed

+482
-90
lines changed

29 files changed

+482
-90
lines changed

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

Lines changed: 145 additions & 49 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;
@@ -131,8 +133,11 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
131133
$this->classes[] = $childClass;
132134

133135
$property = $class->addProperty($node->getName(), $childClass->getFqcn());
134-
$body = '
135-
public function NAME(array $value = []): CLASS
136+
$nodeTypes = $this->getParameterTypes($node);
137+
138+
if (['array'] === $nodeTypes) {
139+
$body = '
140+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
136141
{
137142
if (null === $this->PROPERTY) {
138143
$this->PROPERTY = new CLASS($value);
@@ -142,8 +147,33 @@ public function NAME(array $value = []): CLASS
142147
143148
return $this->PROPERTY;
144149
}';
150+
} else {
151+
$body = '
152+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
153+
{
154+
if (null === $this->PROPERTY) {
155+
if (\is_array($value)) {
156+
$this->PROPERTY = new CLASS($value);
157+
} else {
158+
$this->PROPERTY = $value;
159+
}
160+
} elseif (!$this->PROPERTY instanceof CLASS) {
161+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized with a scalar value. You cannot call NAME() anymore.\');
162+
} elseif ([] !== $value) {
163+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
164+
}
165+
166+
return $this->PROPERTY;
167+
}';
168+
}
169+
145170
$class->addUse(InvalidConfigurationException::class);
146-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
171+
$class->addMethod($node->getName(), $body, [
172+
'PROPERTY' => $property->getName(),
173+
'CLASS' => $childClass->getFqcn(),
174+
'RETURN_TYPEHINT' => ['array'] === $nodeTypes ? $childClass->getFqcn() : 'self|'.$childClass->getFqcn(),
175+
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes),
176+
]);
147177

148178
$this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
149179
}
@@ -174,39 +204,53 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
174204
$prototype = $node->getPrototype();
175205
$methodName = $name;
176206

177-
$parameterType = $this->getParameterType($prototype);
178-
if (null !== $parameterType || $prototype instanceof ScalarNode) {
207+
$nodeTypes = $this->getParameterTypes($node);
208+
$prototypeTypes = $this->getParameterTypes($prototype);
209+
210+
$isObject = $prototype instanceof ArrayNode && (!$prototype instanceof PrototypedArrayNode || !$prototype->getPrototype() instanceof ScalarNode);
211+
if (!$isObject) {
179212
$class->addUse(ParamConfigurator::class);
180213
$property = $class->addProperty($node->getName());
181214
if (null === $key = $node->getKeyAttribute()) {
182215
// This is an array of values; don't use singular name
216+
$nodeTypesWithoutArray = array_filter($nodeTypes, static fn ($type) => 'array' !== $type);
183217
$body = '
184218
/**
185-
* @param ParamConfigurator|list<ParamConfigurator|TYPE> $value
219+
* @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
186220
*
187221
* @return $this
188222
*/
189-
public function NAME(ParamConfigurator|array $value): static
223+
public function NAME(PARAM_TYPE $value): static
190224
{
191225
$this->PROPERTY = $value;
192226
193227
return $this;
194228
}';
195229

196-
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType]);
230+
$class->addMethod($node->getName(), $body, [
231+
'PROPERTY' => $property->getName(),
232+
'PROTOTYPE_TYPE' => implode('|', $prototypeTypes),
233+
'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '',
234+
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeTypes),
235+
]);
197236
} else {
198237
$body = '
199238
/**
200239
* @return $this
201240
*/
202-
public function NAME(string $VAR, TYPE $VALUE): static
241+
public function NAME(string $VAR, PARAM_TYPE $VALUE): static
203242
{
204243
$this->PROPERTY[$VAR] = $VALUE;
205244
206245
return $this;
207246
}';
208247

209-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : 'ParamConfigurator|'.$parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
248+
$class->addMethod($methodName, $body, [
249+
'PROPERTY' => $property->getName(),
250+
'VAR' => '' === $key ? 'key' : $key,
251+
'VALUE' => 'value' === $key ? 'data' : 'value',
252+
'PARAM_TYPE' => \in_array('mixed', $prototypeTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeTypes),
253+
]);
210254
}
211255

212256
return;
@@ -216,20 +260,41 @@ public function NAME(string $VAR, TYPE $VALUE): static
216260
if ($prototype instanceof ArrayNode) {
217261
$childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys());
218262
}
263+
219264
$class->addRequire($childClass);
220265
$this->classes[] = $childClass;
221266
$property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]');
222267

223268
if (null === $key = $node->getKeyAttribute()) {
224-
$body = '
225-
public function NAME(array $value = []): CLASS
269+
if (['array'] === $nodeTypes) {
270+
$body = '
271+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
226272
{
227273
return $this->PROPERTY[] = new CLASS($value);
228274
}';
229-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
275+
} else {
276+
$body = '
277+
public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
278+
{
279+
if (\is_array($value)) {
280+
return $this->PROPERTY[] = new CLASS($value);
281+
}
282+
283+
$this->PROPERTY[] = $value;
284+
return $this;
285+
}';
286+
}
287+
288+
$class->addMethod($methodName, $body, [
289+
'PROPERTY' => $property->getName(),
290+
'CLASS' => $childClass->getFqcn(),
291+
'RETURN_TYPEHINT' => ['array'] === $nodeTypes ? $childClass->getFqcn() : 'self|'.$childClass->getFqcn(),
292+
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes),
293+
]);
230294
} else {
231-
$body = '
232-
public function NAME(string $VAR, array $VALUE = []): CLASS
295+
if (['array'] === $nodeTypes) {
296+
$body = '
297+
public function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS
233298
{
234299
if (!isset($this->PROPERTY[$VAR])) {
235300
return $this->PROPERTY[$VAR] = new CLASS($value);
@@ -240,8 +305,39 @@ public function NAME(string $VAR, array $VALUE = []): CLASS
240305
241306
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
242307
}';
308+
} else {
309+
$body = '
310+
public function NAME(string $VAR, PARAM_TYPE $VALUE = []): RETURN_TYPEHINT
311+
{
312+
if (!isset($this->PROPERTY[$VAR])) {
313+
if (\is_array($VALUE)) {
314+
return $this->PROPERTY[$VAR] = new CLASS($value);
315+
} else {
316+
$this->PROPERTY[$VAR] = $VALUE;
317+
318+
return $this;
319+
}
320+
}
321+
if (!$this->PROPERTY[$VAR] instanceof CLASS) {
322+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized with a scalar value. You cannot call NAME() anymore.\');
323+
}
324+
if ([] === $VALUE) {
325+
return $this->PROPERTY[$VAR];
326+
}
327+
328+
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
329+
}';
330+
}
331+
243332
$class->addUse(InvalidConfigurationException::class);
244-
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
333+
$class->addMethod($methodName, $body, [
334+
'PROPERTY' => $property->getName(),
335+
'CLASS' => $childClass->getFqcn(),
336+
'RETURN_TYPEHINT' => ['array'] === $nodeTypes ? $childClass->getFqcn() : 'self|'.$childClass->getFqcn(),
337+
'VAR' => '' === $key ? 'key' : $key,
338+
'VALUE' => 'value' === $key ? 'data' : 'value',
339+
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes),
340+
]);
245341
}
246342

247343
$this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName());
@@ -267,35 +363,35 @@ public function NAME($value): static
267363
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
268364
}
269365

270-
private function getParameterType(NodeInterface $node): ?string
366+
private function getParameterTypes(NodeInterface $node): array
271367
{
272-
if ($node instanceof BooleanNode) {
273-
return 'bool';
274-
}
275-
276-
if ($node instanceof IntegerNode) {
277-
return 'int';
278-
}
279-
280-
if ($node instanceof FloatNode) {
281-
return 'float';
282-
}
283-
284-
if ($node instanceof EnumNode) {
285-
return '';
286-
}
368+
$paramTypes = [];
369+
if ($node instanceof BaseNode) {
370+
$types = $node->getNormalizedTypes();
371+
if (\in_array(ExprBuilder::TYPE_ANY, $types, true)) {
372+
$paramTypes[] = 'mixed';
373+
}
287374

288-
if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) {
289-
// This is just an array of variables
290-
return 'array';
375+
if (\in_array(ExprBuilder::TYPE_STRING, $types, true)) {
376+
$paramTypes[] = 'string';
377+
}
291378
}
292379

293-
if ($node instanceof VariableNode) {
294-
// mixed
295-
return '';
380+
if ($node instanceof BooleanNode) {
381+
$paramTypes[] = 'bool';
382+
} elseif ($node instanceof IntegerNode) {
383+
$paramTypes[] = 'int';
384+
} elseif ($node instanceof FloatNode) {
385+
$paramTypes[] = 'float';
386+
} elseif ($node instanceof EnumNode) {
387+
$paramTypes[] = 'mixed';
388+
} elseif ($node instanceof ArrayNode) {
389+
$paramTypes[] = 'array';
390+
} elseif ($node instanceof VariableNode) {
391+
$paramTypes[] = 'mixed';
296392
}
297393

298-
return null;
394+
return array_unique($paramTypes);
299395
}
300396

301397
private function getComment(VariableNode $node): string
@@ -318,11 +414,8 @@ private function getComment(VariableNode $node): string
318414
return var_export($a, true);
319415
}, $node->getValues())))."\n";
320416
} else {
321-
$parameterType = $this->getParameterType($node);
322-
if (null === $parameterType || '' === $parameterType) {
323-
$parameterType = 'mixed';
324-
}
325-
$comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'."\n";
417+
$parameterTypes = $this->getParameterTypes($node);
418+
$comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n";
326419
}
327420

328421
if ($node->isDeprecated()) {
@@ -361,16 +454,20 @@ private function buildToArray(ClassBuilder $class): void
361454
$code = '$this->PROPERTY';
362455
if (null !== $p->getType()) {
363456
if ($p->isArray()) {
364-
$code = 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)';
457+
$code = 'array_map(function ($v) { return $v instanceof CLASS ? $v->toArray() : $v; }, $this->PROPERTY)';
365458
} else {
366-
$code = '$this->PROPERTY->toArray()';
459+
$code = '$this->PROPERTY instanceof CLASS ? $this->PROPERTY->toArray() : $this->PROPERTY';
367460
}
368461
}
369462

370463
$body .= strtr('
371464
if (null !== $this->PROPERTY) {
372465
$output[\'ORG_NAME\'] = '.$code.';
373-
}', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
466+
}', [
467+
'PROPERTY' => $p->getName(),
468+
'ORG_NAME' => $p->getOriginalName(),
469+
'CLASS' => $p->getType(),
470+
]);
374471
}
375472

376473
$extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
@@ -420,8 +517,7 @@ private function buildConstructor(ClassBuilder $class): void
420517

421518
$class->addMethod('__construct', '
422519
public function __construct(array $value = [])
423-
{
424-
'.$body.'
520+
{'.$body.'
425521
}');
426522
}
427523

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)