Skip to content

Commit ebf4d0a

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

File tree

11 files changed

+305
-64
lines changed

11 files changed

+305
-64
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) => $type !== 'array');
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)
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