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
+ $ nodeTypes =$ this ->getParameterTypes ($ node );
137
+
138
+ if (['array ' ] ===$ nodeTypes ) {
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 ' ] ===$ nodeTypes ?$ childClass ->getFqcn () :'self| ' .$ childClass ->getFqcn (),
175
+ 'PARAM_TYPE ' =>\in_array ('mixed ' ,$ nodeTypes ,true ) ?'mixed ' :implode ('| ' ,$ nodeTypes ),
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 ||$ prototypeinstanceof ScalarNode) {
207
+ $ nodeTypes =$ this ->getParameterTypes ($ node );
208
+ $ prototypeTypes =$ this ->getParameterTypes ($ prototype );
209
+
210
+ $ isObject =$ prototypeinstanceof ArrayNode && (!$ prototypeinstanceof 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
+ $ nodeTypesWithoutArray =array_filter ($ nodeTypes ,static fn ($ type ) =>'array ' !==$ type );
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 ' =>implode ('| ' ,$ prototypeTypes ),
233
+ 'EXTRA_TYPE ' =>$ nodeTypesWithoutArray ?'| ' .implode ('| ' ,$ nodeTypesWithoutArray ) :'' ,
234
+ 'PARAM_TYPE ' =>\in_array ('mixed ' ,$ nodeTypes ,true ) ?'mixed ' :'ParamConfigurator| ' .implode ('| ' ,$ nodeTypes ),
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 ' =>\in_array ('mixed ' ,$ prototypeTypes ,true ) ?'mixed ' :'ParamConfigurator| ' .implode ('| ' ,$ prototypeTypes ),
253
+ ]);
210
254
}
211
255
212
256
return ;
@@ -216,20 +260,41 @@ public function NAME(string $VAR, TYPE $VALUE): static
216
260
if ($ prototypeinstanceof 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 ' ] ===$ nodeTypes ) {
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 ' ] ===$ nodeTypes ?$ childClass ->getFqcn () :'self| ' .$ childClass ->getFqcn (),
292
+ 'PARAM_TYPE ' =>\in_array ('mixed ' ,$ nodeTypes ,true ) ?'mixed ' :implode ('| ' ,$ nodeTypes ),
293
+ ]);
230
294
}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
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 ' ] ===$ 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
+ ]);
245
341
}
246
342
247
343
$ this ->buildNode ($ prototype ,$ childClass ,$ namespace .'\\' .$ childClass ->getName ());
@@ -267,35 +363,35 @@ 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 getParameterTypes (NodeInterface $ node ):array
271
367
{
272
- if ($ nodeinstanceof BooleanNode) {
273
- return 'bool ' ;
274
- }
275
-
276
- if ($ nodeinstanceof IntegerNode) {
277
- return 'int ' ;
278
- }
279
-
280
- if ($ nodeinstanceof FloatNode) {
281
- return 'float ' ;
282
- }
283
-
284
- if ($ nodeinstanceof EnumNode) {
285
- return '' ;
286
- }
368
+ $ paramTypes = [];
369
+ if ($ nodeinstanceof BaseNode) {
370
+ $ types =$ node ->getNormalizedTypes ();
371
+ if (\in_array (ExprBuilder::TYPE_ANY ,$ types ,true )) {
372
+ $ paramTypes [] ='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
+ }
291
378
}
292
379
293
- if ($ nodeinstanceof VariableNode) {
294
- // mixed
295
- return '' ;
380
+ if ($ nodeinstanceof BooleanNode) {
381
+ $ paramTypes [] ='bool ' ;
382
+ }elseif ($ nodeinstanceof IntegerNode) {
383
+ $ paramTypes [] ='int ' ;
384
+ }elseif ($ nodeinstanceof FloatNode) {
385
+ $ paramTypes [] ='float ' ;
386
+ }elseif ($ nodeinstanceof EnumNode) {
387
+ $ paramTypes [] ='mixed ' ;
388
+ }elseif ($ nodeinstanceof ArrayNode) {
389
+ $ paramTypes [] ='array ' ;
390
+ }elseif ($ nodeinstanceof VariableNode) {
391
+ $ paramTypes [] ='mixed ' ;
296
392
}
297
393
298
- return null ;
394
+ return array_unique ( $ paramTypes ) ;
299
395
}
300
396
301
397
private function getComment (VariableNode $ node ):string
@@ -318,11 +414,8 @@ private function getComment(VariableNode $node): string
318
414
return var_export ($ a ,true );
319
415
},$ node ->getValues ())))."\n" ;
320
416
}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" ;
326
419
}
327
420
328
421
if ($ node ->isDeprecated ()) {
@@ -361,16 +454,20 @@ private function buildToArray(ClassBuilder $class): void
361
454
$ code ='$this->PROPERTY ' ;
362
455
if (null !==$ p ->getType ()) {
363
456
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) ' ;
365
458
}else {
366
- $ code ='$this->PROPERTY-> toArray() ' ;
459
+ $ code ='$this->PROPERTY instanceof CLASS ? $this->PROPERTY-> toArray() : $this->PROPERTY ' ;
367
460
}
368
461
}
369
462
370
463
$ body .=strtr ('
371
464
if (null !== $this->PROPERTY) {
372
465
$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
+ ]);
374
471
}
375
472
376
473
$ extraKeys =$ class ->shouldAllowExtraKeys () ?' + $this->_extraKeys ' :'' ;
@@ -420,8 +517,7 @@ private function buildConstructor(ClassBuilder $class): void
420
517
421
518
$ class ->addMethod ('__construct ' ,'
422
519
public function __construct(array $value = [])
423
- {
424
- ' .$ body .'
520
+ { ' .$ body .'
425
521
} ' );
426
522
}
427
523