1212namespace Symfony \Component \Config \Builder ;
1313
1414use Symfony \Component \Config \Definition \ArrayNode ;
15+ use Symfony \Component \Config \Definition \BaseNode ;
1516use Symfony \Component \Config \Definition \BooleanNode ;
17+ use Symfony \Component \Config \Definition \Builder \ExprBuilder ;
1618use Symfony \Component \Config \Definition \ConfigurationInterface ;
1719use Symfony \Component \Config \Definition \EnumNode ;
1820use 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 ));
0 commit comments