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 ;
@@ -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 ' , '
422519public function __construct(array $value = [])
423- {
424- ' .$ body .'
520+ { ' .$ body .'
425521} ' );
426522 }
427523
0 commit comments