12
12
namespace Symfony \Component \OptionsResolver ;
13
13
14
14
use Symfony \Component \OptionsResolver \Exception \AccessException ;
15
+ use Symfony \Component \OptionsResolver \Exception \ExceptionInterface ;
15
16
use Symfony \Component \OptionsResolver \Exception \InvalidOptionsException ;
16
17
use Symfony \Component \OptionsResolver \Exception \MissingOptionsException ;
17
18
use Symfony \Component \OptionsResolver \Exception \NoSuchOptionException ;
@@ -40,6 +41,13 @@ class OptionsResolver implements Options
40
41
*/
41
42
private $ defaults = array ();
42
43
44
+ /**
45
+ * The nested options.
46
+ *
47
+ * @var OptionsResolver[]
48
+ */
49
+ private $ nested = array ();
50
+
43
51
/**
44
52
* The names of required options.
45
53
*
@@ -142,10 +150,23 @@ class OptionsResolver implements Options
142
150
* is spread across different locations of your code, such as base and
143
151
* sub-classes.
144
152
*
153
+ * If you set default values of nested options, this method will return the
154
+ * nested instance for the same convenience as above.
155
+ *
156
+ * // Master class
157
+ * $options->setNested('connexion', array('port' => '80'));
158
+ *
159
+ * // Sub class inheriting $options
160
+ * $nestedOptions = $options->setDefault('connexion', array(
161
+ * 'port' => '443', // overrides default
162
+ * ));
163
+ *
164
+ * $nestedOptions->setRequired('type');
165
+ *
145
166
* @param string $option The name of the option
146
167
* @param mixed $value The default value of the option
147
168
*
148
- * @return OptionsResolver This instance
169
+ * @return OptionsResolver This instance or the nested instance
149
170
*
150
171
* @throws AccessException If called from a lazy option or normalizer
151
172
*/
@@ -167,7 +188,7 @@ public function setDefault($option, $value)
167
188
if (isset ($ params [0 ]) && null !== ($ class = $ params [0 ]->getClass ()) && Options::class === $ class ->name ) {
168
189
// Initialize the option if no previous value exists
169
190
if (!isset ($ this ->defaults [$ option ])) {
170
- $ this ->defaults [$ option ] = null ;
191
+ $ this ->defaults [$ option ] = $ this -> isNested ( $ option ) ? array () : null ;
171
192
}
172
193
173
194
// Ignore previous lazy options if the closure has no second parameter
@@ -189,6 +210,18 @@ public function setDefault($option, $value)
189
210
// This option is not lazy anymore
190
211
unset($ this ->lazy [$ option ]);
191
212
213
+ if ($ this ->isNested ($ option )) {
214
+ $ defaults = isset ($ this ->defaults [$ option ]) ? $ this ->defaults [$ option ] : array ();
215
+ $ this ->defaults [$ option ] = array_replace ($ defaults , $ value );
216
+ $ this ->defined [$ option ] = true ;
217
+ // Make sure the nested options are processed
218
+ unset($ this ->resolved [$ option ]);
219
+
220
+ // Returning the nested options here is convenient when we need to
221
+ // override them from a sub class
222
+ return $ this ->nested [$ option ];
223
+ }
224
+
192
225
// Yet undefined options can be marked as resolved, because we only need
193
226
// to resolve options with lazy closures, normalizers or validation
194
227
// rules, none of which can exist for undefined options
@@ -236,6 +269,82 @@ public function hasDefault($option)
236
269
return array_key_exists ($ option , $ this ->defaults );
237
270
}
238
271
272
+ /**
273
+ * Defines an option as a new self.
274
+ *
275
+ * Returns a new OptionsResolver instance to configure nested options.
276
+ *
277
+ * $nestedOptions = $options->setNested('connexion', array(
278
+ * 'host' => 'localhost',
279
+ * 'port' => 80,
280
+ * );
281
+ *
282
+ * $nestedOptions->setRequired('user');
283
+ * $nestedOptions->setDefault('password', function (Options $nested) {
284
+ * return isset($nested['user']) ? '' : null;
285
+ * });
286
+ * $nestedOptions->setDefined(array('secure'));
287
+ * $nestedOptions->setNormalizer('secure', function (Options $nested, $secure) {
288
+ * return 443 === $nested['port'] ?: $secure;
289
+ * });
290
+ * $nestedOptions->setAllowedTypes('port', 'int');
291
+ *
292
+ * @param string $option The option name
293
+ * @param array $defaults The default nested options
294
+ *
295
+ * @return OptionsResolver The nested options resolver
296
+ *
297
+ * @throws AccessException If called from a lazy option or normalizer
298
+ */
299
+ public function setNested ($ option , array $ defaults = array ())
300
+ {
301
+ if ($ this ->locked ) {
302
+ throw new AccessException ('Options cannot be made nested from a lazy option or normalizer. ' );
303
+ }
304
+
305
+ $ nestedOptions = new self ();
306
+
307
+ foreach ($ defaults as $ name => $ default ) {
308
+ $ nestedOptions ->setDefault ($ name , $ default );
309
+ }
310
+
311
+ // Keep a raw copy of defaults until nested options are resolved allowing to
312
+ // easily override them, even using lazy definition with {@link setDefault()}
313
+ $ this ->defaults [$ option ] = $ defaults ;
314
+ $ this ->defined [$ option ] = true ;
315
+
316
+ // Make sure the nested options are processed
317
+ unset($ this ->resolved [$ option ]);
318
+
319
+ return $ this ->nested [$ option ] = $ nestedOptions ;
320
+ }
321
+
322
+ /**
323
+ * Returns whether an option is nested.
324
+ *
325
+ * An option is nested if it was passed to {@link setNested()}.
326
+ *
327
+ * @param string $option The name of the option
328
+ *
329
+ * @return bool Whether the option is nested
330
+ */
331
+ public function isNested ($ option )
332
+ {
333
+ return isset ($ this ->nested [$ option ]);
334
+ }
335
+
336
+ /**
337
+ * Returns the names of all nested options.
338
+ *
339
+ * @return string[] The names of the nested options
340
+ *
341
+ * @see isNested()
342
+ */
343
+ public function getNestedOptions ()
344
+ {
345
+ return array_keys ($ this ->nested );
346
+ }
347
+
239
348
/**
240
349
* Marks one or more options as required.
241
350
*
@@ -443,6 +552,11 @@ public function setAllowedValues($option, $allowedValues)
443
552
throw new AccessException ('Allowed values cannot be set from a lazy option or normalizer. ' );
444
553
}
445
554
555
+ // Not supported for nested options
556
+ if ($ this ->isNested ($ option )) {
557
+ return $ this ;
558
+ }
559
+
446
560
if (!isset ($ this ->defined [$ option ])) {
447
561
throw new UndefinedOptionsException (sprintf (
448
562
'The option "%s" does not exist. Defined options are: "%s". ' ,
@@ -488,6 +602,11 @@ public function addAllowedValues($option, $allowedValues)
488
602
throw new AccessException ('Allowed values cannot be added from a lazy option or normalizer. ' );
489
603
}
490
604
605
+ // Not supported for nested options
606
+ if ($ this ->isNested ($ option )) {
607
+ return $ this ;
608
+ }
609
+
491
610
if (!isset ($ this ->defined [$ option ])) {
492
611
throw new UndefinedOptionsException (sprintf (
493
612
'The option "%s" does not exist. Defined options are: "%s". ' ,
@@ -533,6 +652,11 @@ public function setAllowedTypes($option, $allowedTypes)
533
652
throw new AccessException ('Allowed types cannot be set from a lazy option or normalizer. ' );
534
653
}
535
654
655
+ // Not supported for nested options
656
+ if ($ this ->isNested ($ option )) {
657
+ return $ this ;
658
+ }
659
+
536
660
if (!isset ($ this ->defined [$ option ])) {
537
661
throw new UndefinedOptionsException (sprintf (
538
662
'The option "%s" does not exist. Defined options are: "%s". ' ,
@@ -572,6 +696,11 @@ public function addAllowedTypes($option, $allowedTypes)
572
696
throw new AccessException ('Allowed types cannot be added from a lazy option or normalizer. ' );
573
697
}
574
698
699
+ // Not supported for nested options
700
+ if ($ this ->isNested ($ option )) {
701
+ return $ this ;
702
+ }
703
+
575
704
if (!isset ($ this ->defined [$ option ])) {
576
705
throw new UndefinedOptionsException (sprintf (
577
706
'The option "%s" does not exist. Defined options are: "%s". ' ,
@@ -610,8 +739,9 @@ public function remove($optionNames)
610
739
}
611
740
612
741
foreach ((array ) $ optionNames as $ option ) {
613
- unset($ this ->defined [$ option ], $ this ->defaults [$ option ], $ this ->required [$ option ], $ this ->resolved [$ option ]);
614
- unset($ this ->lazy [$ option ], $ this ->normalizers [$ option ], $ this ->allowedTypes [$ option ], $ this ->allowedValues [$ option ]);
742
+ unset($ this ->defined [$ option ], $ this ->defaults [$ option ], $ this ->nested [$ option ]);
743
+ unset($ this ->required [$ option ], $ this ->resolved [$ option ], $ this ->lazy [$ option ]);
744
+ unset($ this ->normalizers [$ option ], $ this ->allowedTypes [$ option ], $ this ->allowedValues [$ option ]);
615
745
}
616
746
617
747
return $ this ;
@@ -632,6 +762,7 @@ public function clear()
632
762
633
763
$ this ->defined = array ();
634
764
$ this ->defaults = array ();
765
+ $ this ->nested = array ();
635
766
$ this ->required = array ();
636
767
$ this ->resolved = array ();
637
768
$ this ->lazy = array ();
@@ -691,7 +822,13 @@ public function resolve(array $options = array())
691
822
692
823
// Override options set by the user
693
824
foreach ($ options as $ option => $ value ) {
694
- $ clone ->defaults [$ option ] = $ value ;
825
+ if ($ clone ->isNested ($ option )) {
826
+ $ defaults = isset ($ clone ->defaults [$ option ]) ? $ clone ->defaults [$ option ] : array ();
827
+ $ clone ->defaults [$ option ] = array_replace ($ defaults , $ value );
828
+ } else {
829
+ $ clone ->defaults [$ option ] = $ value ;
830
+ }
831
+
695
832
unset($ clone ->resolved [$ option ], $ clone ->lazy [$ option ]);
696
833
}
697
834
@@ -789,6 +926,14 @@ public function offsetGet($option)
789
926
// END
790
927
}
791
928
929
+ if ($ this ->isNested ($ option )) {
930
+ try {
931
+ $ value = $ this ->nested [$ option ]->resolve ($ value );
932
+ } catch (ExceptionInterface $ e ) {
933
+ throw new InvalidOptionsException (sprintf ('The nested options in the option "%s" could not be resolved. ' , $ option ), 0 , $ e );
934
+ }
935
+ }
936
+
792
937
// Validate the type of the resolved option
793
938
if (isset ($ this ->allowedTypes [$ option ])) {
794
939
$ valid = false ;
@@ -955,6 +1100,13 @@ public function count()
955
1100
return count ($ this ->defaults );
956
1101
}
957
1102
1103
+ public function __clone ()
1104
+ {
1105
+ foreach ($ this ->nested as $ name => $ options ) {
1106
+ $ this ->nested [$ name ] = clone $ options ;
1107
+ }
1108
+ }
1109
+
958
1110
/**
959
1111
* Returns a string representation of the type of the value.
960
1112
*
0 commit comments