12
12
namespace Symfony \Component \Config \Builder ;
13
13
14
14
use Symfony \Component \Config \Definition \ArrayNode ;
15
+ use Symfony \Component \Config \Definition \BaseNode ;
15
16
use Symfony \Component \Config \Definition \BooleanNode ;
17
+ use Symfony \Component \Config \Definition \Builder \ExprBuilder ;
16
18
use Symfony \Component \Config \Definition \ConfigurationInterface ;
17
19
use Symfony \Component \Config \Definition \EnumNode ;
18
20
use Symfony \Component \Config \Definition \Exception \InvalidConfigurationException ;
@@ -131,8 +133,11 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
131
133
$ this ->classes [] = $ childClass ;
132
134
133
135
$ property = $ class ->addProperty ($ node ->getName (), $ childClass ->getFqcn ());
134
- $ body = '
135
- public function NAME(array $value = []): CLASS
136
+ $ nodeType = $ this ->getParameterType ($ node );
137
+
138
+ if ('array ' === $ nodeType ) {
139
+ $ body = '
140
+ public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
136
141
{
137
142
if (null === $this->PROPERTY) {
138
143
$this->PROPERTY = new CLASS($value);
@@ -142,8 +147,33 @@ public function NAME(array $value = []): CLASS
142
147
143
148
return $this->PROPERTY;
144
149
} ' ;
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
+
145
170
$ 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 ' === $ nodeType ? $ childClass ->getFqcn () : 'self| ' .$ childClass ->getFqcn (),
175
+ 'PARAM_TYPE ' => $ nodeType ,
176
+ ]);
147
177
148
178
$ this ->buildNode ($ node , $ childClass , $ this ->getSubNamespace ($ childClass ));
149
179
}
@@ -174,39 +204,53 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
174
204
$ prototype = $ node ->getPrototype ();
175
205
$ methodName = $ name ;
176
206
177
- $ parameterType = $ this ->getParameterType ($ prototype );
178
- if (null !== $ parameterType || $ prototype instanceof ScalarNode) {
207
+ $ nodeType = $ this ->getParameterType ($ node );
208
+ $ prototypeType = $ this ->getParameterType ($ prototype );
209
+
210
+ $ isObject = $ prototype instanceof ArrayNode && (!$ prototype instanceof PrototypedArrayNode || !$ prototype ->getPrototype () instanceof ScalarNode);
211
+ if (!$ isObject ) {
179
212
$ class ->addUse (ParamConfigurator::class);
180
213
$ property = $ class ->addProperty ($ node ->getName ());
181
214
if (null === $ key = $ node ->getKeyAttribute ()) {
182
215
// This is an array of values; don't use singular name
216
+ $ nodeTypeWithoutArray = implode ('| ' , array_filter (explode ('| ' , $ nodeType ), fn ($ type ) => $ type !== 'array ' ));
183
217
$ body = '
184
218
/**
185
- * @param ParamConfigurator|list<ParamConfigurator|TYPE> $value
219
+ * @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
186
220
*
187
221
* @return $this
188
222
*/
189
- public function NAME(ParamConfigurator|array $value): static
223
+ public function NAME(PARAM_TYPE $value): static
190
224
{
191
225
$this->PROPERTY = $value;
192
226
193
227
return $this;
194
228
} ' ;
195
229
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 ' => $ prototypeType ,
233
+ 'EXTRA_TYPE ' => $ nodeTypeWithoutArray ? '| ' .$ nodeTypeWithoutArray : '' ,
234
+ 'PARAM_TYPE ' => $ nodeType === 'mixed ' ? 'mixed ' : 'ParamConfigurator| ' .$ nodeType ,
235
+ ]);
197
236
} else {
198
237
$ body = '
199
238
/**
200
239
* @return $this
201
240
*/
202
- public function NAME(string $VAR, TYPE $VALUE): static
241
+ public function NAME(string $VAR, PARAM_TYPE $VALUE): static
203
242
{
204
243
$this->PROPERTY[$VAR] = $VALUE;
205
244
206
245
return $this;
207
246
} ' ;
208
247
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 ' => $ prototypeType === 'mixed ' ? 'mixed ' : 'ParamConfigurator| ' .$ prototypeType ,
253
+ ]);
210
254
}
211
255
212
256
return ;
@@ -216,20 +260,41 @@ public function NAME(string $VAR, TYPE $VALUE): static
216
260
if ($ prototype instanceof ArrayNode) {
217
261
$ childClass ->setAllowExtraKeys ($ prototype ->shouldIgnoreExtraKeys ());
218
262
}
263
+
219
264
$ class ->addRequire ($ childClass );
220
265
$ this ->classes [] = $ childClass ;
221
266
$ property = $ class ->addProperty ($ node ->getName (), $ childClass ->getFqcn ().'[] ' );
222
267
223
268
if (null === $ key = $ node ->getKeyAttribute ()) {
224
- $ body = '
225
- public function NAME(array $value = []): CLASS
269
+ if ('array ' === $ nodeType ) {
270
+ $ body = '
271
+ public function NAME(PARAM_TYPE $value = []): RETURN_TYPEHINT
226
272
{
227
273
return $this->PROPERTY[] = new CLASS($value);
228
274
} ' ;
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 ' === $ nodeType ? $ childClass ->getFqcn () : 'self| ' .$ childClass ->getFqcn (),
292
+ 'PARAM_TYPE ' => $ nodeType ,
293
+ ]);
230
294
} else {
231
- $ body = '
232
- public function NAME(string $VAR, array $VALUE = []): CLASS
295
+ if ('array ' === $ nodeType ) {
296
+ $ body = '
297
+ public function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS
233
298
{
234
299
if (!isset($this->PROPERTY[$VAR])) {
235
300
return $this->PROPERTY[$VAR] = new CLASS($value);
@@ -240,8 +305,39 @@ public function NAME(string $VAR, array $VALUE = []): CLASS
240
305
241
306
throw new InvalidConfigurationException( \'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME(). \');
242
307
} ' ;
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
+
243
332
$ 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 ' === $ nodeType ? $ childClass ->getFqcn () : 'self| ' .$ childClass ->getFqcn (),
337
+ 'VAR ' => '' === $ key ? 'key ' : $ key ,
338
+ 'VALUE ' => 'value ' === $ key ? 'data ' : 'value ' ,
339
+ 'PARAM_TYPE ' => $ nodeType ,
340
+ ]);
245
341
}
246
342
247
343
$ this ->buildNode ($ prototype , $ childClass , $ namespace .'\\' .$ childClass ->getName ());
@@ -267,35 +363,38 @@ public function NAME($value): static
267
363
$ class ->addMethod ($ node ->getName (), $ body , ['PROPERTY ' => $ property ->getName (), 'COMMENT ' => $ comment ]);
268
364
}
269
365
270
- private function getParameterType (NodeInterface $ node ): ? string
366
+ private function getParameterType (NodeInterface $ node ): string
271
367
{
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
+ return 'mixed ' ;
373
+ }
287
374
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
+ }
378
+ if (\in_array (ExprBuilder::TYPE_NULL , $ types , true )) {
379
+ $ paramTypes [] = 'null ' ;
380
+ }
291
381
}
292
382
293
- if ($ node instanceof VariableNode) {
294
- // mixed
295
- return '' ;
383
+ if ($ node instanceof BooleanNode) {
384
+ $ paramTypes [] = 'bool ' ;
385
+ } elseif ($ node instanceof IntegerNode) {
386
+ $ paramTypes [] = 'int ' ;
387
+ } elseif ($ node instanceof FloatNode) {
388
+ $ paramTypes [] = 'float ' ;
389
+ } elseif ($ node instanceof EnumNode) {
390
+ $ paramTypes [] = 'mixed ' ;
391
+ } elseif ($ node instanceof ArrayNode) {
392
+ $ paramTypes [] = 'array ' ;
393
+ } elseif ($ node instanceof VariableNode) {
394
+ $ paramTypes [] = 'mixed ' ;
296
395
}
297
396
298
- return null ;
397
+ return implode ( ' | ' , $ paramTypes ) ;
299
398
}
300
399
301
400
private function getComment (VariableNode $ node ): string
@@ -319,7 +418,7 @@ private function getComment(VariableNode $node): string
319
418
}, $ node ->getValues ())))."\n" ;
320
419
} else {
321
420
$ parameterType = $ this ->getParameterType ($ node );
322
- if (null === $ parameterType || '' === $ parameterType ) {
421
+ if (null === $ parameterType ) {
323
422
$ parameterType = 'mixed ' ;
324
423
}
325
424
$ comment .= ' * @param ParamConfigurator| ' .$ parameterType .' $value ' ."\n" ;
@@ -361,16 +460,20 @@ private function buildToArray(ClassBuilder $class): void
361
460
$ code = '$this->PROPERTY ' ;
362
461
if (null !== $ p ->getType ()) {
363
462
if ($ p ->isArray ()) {
364
- $ code = 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY) ' ;
463
+ $ code = 'array_map(function ($v) { return $v instanceof CLASS ? $v ->toArray() : $v ; }, $this->PROPERTY) ' ;
365
464
} else {
366
- $ code = '$this->PROPERTY-> toArray() ' ;
465
+ $ code = '$this->PROPERTY instanceof CLASS ? $this->PROPERTY-> toArray() : $this->PROPERTY ' ;
367
466
}
368
467
}
369
468
370
469
$ body .= strtr ('
371
470
if (null !== $this->PROPERTY) {
372
471
$output[ \'ORG_NAME \'] = ' .$ code .';
373
- } ' , ['PROPERTY ' => $ p ->getName (), 'ORG_NAME ' => $ p ->getOriginalName ()]);
472
+ } ' , [
473
+ 'PROPERTY ' => $ p ->getName (),
474
+ 'ORG_NAME ' => $ p ->getOriginalName (),
475
+ 'CLASS ' => $ p ->getType (),
476
+ ]);
374
477
}
375
478
376
479
$ extraKeys = $ class ->shouldAllowExtraKeys () ? ' + $this->_extraKeys ' : '' ;
@@ -420,8 +523,7 @@ private function buildConstructor(ClassBuilder $class): void
420
523
421
524
$ class ->addMethod ('__construct ' , '
422
525
public function __construct(array $value = [])
423
- {
424
- ' .$ body .'
526
+ { ' .$ body .'
425
527
} ' );
426
528
}
427
529
0 commit comments