15
15
use Symfony \Component \Config \Definition \Exception \ForbiddenOverwriteException ;
16
16
use Symfony \Component \Config \Definition \Exception \InvalidConfigurationException ;
17
17
use Symfony \Component \Config \Definition \Exception \InvalidTypeException ;
18
+ use Symfony \Component \Config \Definition \Exception \UnsetKeyException ;
18
19
19
20
/**
20
21
* The base node class.
@@ -25,6 +26,9 @@ abstract class BaseNode implements NodeInterface
25
26
{
26
27
const DEFAULT_PATH_SEPARATOR = '. ' ;
27
28
29
+ private static $ placeholderUniquePrefix ;
30
+ private static $ placeholders = array ();
31
+
28
32
protected $ name ;
29
33
protected $ parent ;
30
34
protected $ normalizationClosures = array ();
@@ -36,6 +40,8 @@ abstract class BaseNode implements NodeInterface
36
40
protected $ attributes = array ();
37
41
protected $ pathSeparator ;
38
42
43
+ private $ handlingPlaceholder ;
44
+
39
45
/**
40
46
* @throws \InvalidArgumentException if the name contains a period
41
47
*/
@@ -50,6 +56,47 @@ public function __construct(?string $name, NodeInterface $parent = null, string
50
56
$ this ->pathSeparator = $ pathSeparator ;
51
57
}
52
58
59
+ /**
60
+ * Register possible (dummy) values for a dynamic placeholder value.
61
+ *
62
+ * Matching configuration values will be processed with a provided value, one by one. After a provided value is
63
+ * successfully processed the configuration value is returned as is, thus preserving the placeholder.
64
+ *
65
+ * @internal
66
+ */
67
+ public static function setPlaceholder (string $ placeholder , array $ values ): void
68
+ {
69
+ if (!$ values ) {
70
+ throw new \InvalidArgumentException ('At least one value must be provided. ' );
71
+ }
72
+
73
+ self ::$ placeholders [$ placeholder ] = $ values ;
74
+ }
75
+
76
+ /**
77
+ * Sets a common prefix for dynamic placeholder values.
78
+ *
79
+ * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
80
+ * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
81
+ *
82
+ * @internal
83
+ */
84
+ public static function setPlaceholderUniquePrefix (string $ prefix ): void
85
+ {
86
+ self ::$ placeholderUniquePrefix = $ prefix ;
87
+ }
88
+
89
+ /**
90
+ * Resets all current placeholders available.
91
+ *
92
+ * @internal
93
+ */
94
+ public static function resetPlaceholders (): void
95
+ {
96
+ self ::$ placeholderUniquePrefix = null ;
97
+ self ::$ placeholders = array ();
98
+ }
99
+
53
100
public function setAttribute ($ key , $ value )
54
101
{
55
102
$ this ->attributes [$ key ] = $ value ;
@@ -249,8 +296,34 @@ final public function merge($leftSide, $rightSide)
249
296
));
250
297
}
251
298
252
- $ this ->validateType ($ leftSide );
253
- $ this ->validateType ($ rightSide );
299
+ if ($ leftSide !== $ leftPlaceholders = self ::resolvePlaceholderValue ($ leftSide )) {
300
+ foreach ($ leftPlaceholders as $ leftPlaceholder ) {
301
+ $ this ->handlingPlaceholder = $ leftSide ;
302
+ try {
303
+ $ this ->merge ($ leftPlaceholder , $ rightSide );
304
+ } finally {
305
+ $ this ->handlingPlaceholder = null ;
306
+ }
307
+ }
308
+
309
+ return $ rightSide ;
310
+ }
311
+
312
+ if ($ rightSide !== $ rightPlaceholders = self ::resolvePlaceholderValue ($ rightSide )) {
313
+ foreach ($ rightPlaceholders as $ rightPlaceholder ) {
314
+ $ this ->handlingPlaceholder = $ rightSide ;
315
+ try {
316
+ $ this ->merge ($ leftSide , $ rightPlaceholder );
317
+ } finally {
318
+ $ this ->handlingPlaceholder = null ;
319
+ }
320
+ }
321
+
322
+ return $ rightSide ;
323
+ }
324
+
325
+ $ this ->doValidateType ($ leftSide );
326
+ $ this ->doValidateType ($ rightSide );
254
327
255
328
return $ this ->mergeValues ($ leftSide , $ rightSide );
256
329
}
@@ -267,6 +340,20 @@ final public function normalize($value)
267
340
$ value = $ closure ($ value );
268
341
}
269
342
343
+ // resolve placeholder value
344
+ if ($ value !== $ placeholders = self ::resolvePlaceholderValue ($ value )) {
345
+ foreach ($ placeholders as $ placeholder ) {
346
+ $ this ->handlingPlaceholder = $ value ;
347
+ try {
348
+ $ this ->normalize ($ placeholder );
349
+ } finally {
350
+ $ this ->handlingPlaceholder = null ;
351
+ }
352
+ }
353
+
354
+ return $ value ;
355
+ }
356
+
270
357
// replace value with their equivalent
271
358
foreach ($ this ->equivalentValues as $ data ) {
272
359
if ($ data [0 ] === $ value ) {
@@ -275,7 +362,7 @@ final public function normalize($value)
275
362
}
276
363
277
364
// validate type
278
- $ this ->validateType ($ value );
365
+ $ this ->doValidateType ($ value );
279
366
280
367
// normalize value
281
368
return $ this ->normalizeValue ($ value );
@@ -308,7 +395,20 @@ public function getParent()
308
395
*/
309
396
final public function finalize ($ value )
310
397
{
311
- $ this ->validateType ($ value );
398
+ if ($ value !== $ placeholders = self ::resolvePlaceholderValue ($ value )) {
399
+ foreach ($ placeholders as $ placeholder ) {
400
+ $ this ->handlingPlaceholder = $ value ;
401
+ try {
402
+ $ this ->finalize ($ placeholder );
403
+ } finally {
404
+ $ this ->handlingPlaceholder = null ;
405
+ }
406
+ }
407
+
408
+ return $ value ;
409
+ }
410
+
411
+ $ this ->doValidateType ($ value );
312
412
313
413
$ value = $ this ->finalizeValue ($ value );
314
414
@@ -318,6 +418,10 @@ final public function finalize($value)
318
418
try {
319
419
$ value = $ closure ($ value );
320
420
} catch (Exception $ e ) {
421
+ if ($ e instanceof UnsetKeyException && null !== $ this ->handlingPlaceholder ) {
422
+ continue ;
423
+ }
424
+
321
425
throw $ e ;
322
426
} catch (\Exception $ e ) {
323
427
throw new InvalidConfigurationException (sprintf ('Invalid configuration for path "%s": %s ' , $ this ->getPath (), $ e ->getMessage ()), $ e ->getCode (), $ e );
@@ -363,4 +467,85 @@ abstract protected function mergeValues($leftSide, $rightSide);
363
467
* @return mixed The finalized value
364
468
*/
365
469
abstract protected function finalizeValue ($ value );
470
+
471
+ /**
472
+ * Tests if placeholder values are allowed for this node.
473
+ */
474
+ protected function allowPlaceholders (): bool
475
+ {
476
+ return true ;
477
+ }
478
+
479
+ /**
480
+ * Gets allowed dynamic types for this node.
481
+ */
482
+ protected function getValidPlaceholderTypes (): array
483
+ {
484
+ return array ();
485
+ }
486
+
487
+ private static function resolvePlaceholderValue ($ value )
488
+ {
489
+ if (\is_string ($ value )) {
490
+ if (isset (self ::$ placeholders [$ value ])) {
491
+ return self ::$ placeholders [$ value ];
492
+ }
493
+
494
+ if (0 === strpos ($ value , self ::$ placeholderUniquePrefix )) {
495
+ return array ();
496
+ }
497
+ }
498
+
499
+ return $ value ;
500
+ }
501
+
502
+ private static function getType ($ value ): string
503
+ {
504
+ switch ($ type = \gettype ($ value )) {
505
+ case 'boolean ' :
506
+ return 'bool ' ;
507
+ case 'double ' :
508
+ return 'float ' ;
509
+ case 'integer ' :
510
+ return 'int ' ;
511
+ }
512
+
513
+ return $ type ;
514
+ }
515
+
516
+ private function doValidateType ($ value ): void
517
+ {
518
+ if (null === $ this ->handlingPlaceholder || null === $ value ) {
519
+ $ this ->validateType ($ value );
520
+
521
+ return ;
522
+ }
523
+
524
+ if (!$ this ->allowPlaceholders ()) {
525
+ $ e = new InvalidTypeException (sprintf ('A dynamic value is not compatible with a "%s" node type at path "%s". ' , get_class ($ this ), $ this ->getPath ()));
526
+ $ e ->setPath ($ this ->getPath ());
527
+
528
+ throw $ e ;
529
+ }
530
+
531
+ $ knownTypes = array_keys (self ::$ placeholders [$ this ->handlingPlaceholder ]);
532
+ $ validTypes = $ this ->getValidPlaceholderTypes ();
533
+
534
+ if (array_diff ($ knownTypes , $ validTypes )) {
535
+ $ e = new InvalidTypeException (sprintf (
536
+ 'Invalid type for path "%s". Expected %s, but got %s. ' ,
537
+ $ this ->getPath (),
538
+ 1 === count ($ validTypes ) ? '" ' .reset ($ validTypes ).'" ' : 'one of " ' .implode ('", " ' , $ validTypes ).'" ' ,
539
+ 1 === count ($ knownTypes ) ? '" ' .reset ($ knownTypes ).'" ' : 'one of " ' .implode ('", " ' , $ knownTypes ).'" '
540
+ ));
541
+ if ($ hint = $ this ->getInfo ()) {
542
+ $ e ->addHint ($ hint );
543
+ }
544
+ $ e ->setPath ($ this ->getPath ());
545
+
546
+ throw $ e ;
547
+ }
548
+
549
+ $ this ->validateType ($ value );
550
+ }
366
551
}
0 commit comments