11
11
12
12
namespace Symfony \Component \OptionsResolver ;
13
13
14
+ use Symfony \Component \OptionsResolver \Exception \InvalidArgumentException ;
15
+ use Symfony \Component \OptionsResolver \Exception \InvalidOptionsException ;
16
+ use Symfony \Component \OptionsResolver \Exception \MissingOptionsException ;
14
17
use Symfony \Component \OptionsResolver \Exception \OptionDefinitionException ;
15
18
16
19
/**
@@ -56,6 +59,244 @@ class Options implements \ArrayAccess, \Iterator, \Countable
56
59
*/
57
60
priva
6D40
te $ reading = false ;
58
61
62
+ /**
63
+ * Merges options with an array of default values and throws an exception if
64
+ * any of the options does not exist.
65
+ *
66
+ * @param array $options A list of option names and
67
+ * values
68
+ * @param array|Options|OptionsConfig $defaults The accepted options and
69
+ * their default values
70
+ *
71
+ * @return array The merged and validated options
72
+ *
73
+ * @throws InvalidOptionsException If any of the options is not present in
74
+ * the defaults array
75
+ * @throws InvalidArgumentException If the defaults are invalid
76
+ *
77
+ * @since 2.6
78
+ */
79
+ public static function resolve (array $ options , $ defaults )
80
+ {
81
+ if (is_array ($ defaults )) {
82
+ static ::validateNames ($ options , $ defaults , true );
83
+
84
+ return array_replace ($ defaults , $ options );
85
+ }
86
+
87
+ if ($ defaults instanceof self) {
88
+ static ::validateNames ($ options , $ defaults ->options , true );
89
+
90
+ // Make sure this method can be called multiple times
91
+ $ combinedOptions = clone $ defaults ;
92
+
93
+ // Override options set by the user
94
+ foreach ($ options as $ option => $ value ) {
95
+ $ combinedOptions ->set ($ option , $ value );
96
+ }
97
+
98
+ // Resolve options
99
+ return $ combinedOptions ->all ();
100
+ }
101
+
102
+ if ($ defaults instanceof OptionsConfig) {
103
+ static ::validateNames ($ options , $ defaults ->knownOptions , true );
104
+ static ::validateRequired ($ options , $ defaults ->requiredOptions , true );
105
+
106
+ // Make sure this method can be called multiple times
107
+ $ combinedOptions = clone $ defaults ->defaultOptions ;
108
+
109
+ // Override options set by the user
110
+ foreach ($ options as $ option => $ value ) {
111
+ $ combinedOptions ->set ($ option , $ value );
112
+ }
113
+
114
+ // Resolve options
115
+ $ resolvedOptions = $ combinedOptions ->all ();
116
+
117
+ static ::validateTypes ($ resolvedOptions , $ defaults ->allowedTypes );
118
+ static ::validateValues ($ resolvedOptions , $ defaults ->allowedValues );
119
+
120
+ return $ resolvedOptions ;
121
+ }
122
+
123
+ throw new InvalidArgumentException ('The second argument is expected to be given as array, Options instance or OptionsConfig instance. ' );
124
+ }
125
+
126
+ /**
127
+ * Validates that the given option names exist and throws an exception
128
+ * otherwise.
129
+ *
130
+ * @param array $options A list of option names and values
131
+ * @param string|array $acceptedOptions The accepted option(s), either passed
132
+ * as single string or in the values of
133
+ * the given array
134
+ * @param bool $namesAsKeys If set to true, the option names
135
+ * should be passed in the keys of the
136
+ * accepted options array
137
+ *
138
+ * @throws InvalidOptionsException If any of the options is not present in
139
+ * the accepted options
140
+ *
141
+ * @since 2.6
142
+ */
143
+ public static function validateNames (array $ options , $ acceptedOptions , $ namesAsKeys = false )
144
+ {
145
+ $ acceptedOptions = (array ) $ acceptedOptions ;
146
+
147
+ if (!$ namesAsKeys ) {
148
+ $ acceptedOptions = array_flip ($ acceptedOptions );
149
+ }
150
+
151
+ $ diff = array_diff_key ($ options , $ acceptedOptions );
152
+
153
+ if (count ($ diff ) > 0 ) {
154
+ ksort ($ acceptedOptions );
155
+ ksort ($ diff );
156
+
157
+ throw new InvalidOptionsException (sprintf (
158
+ (count ($ diff ) > 1 ? 'The options "%s" do not exist. ' : 'The option "%s" does not exist. ' ).' Known options are: "%s" ' ,
159
+ implode ('", " ' , array_keys ($ diff )),
160
+ implode ('", " ' , array_keys ($ acceptedOptions ))
161
+ ));
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Validates that the required options are given and throws an exception
167
+ * otherwise.
168
+ *
169
+ * The option names may be any strings that don't consist exclusively of
170
+ * digits. For example, "case1" is a valid option name, "1" is not.
171
+ *
172
+ * @param array $options A list of option names and values
173
+ * @param string|array $requiredOptions The required option(s), either
174
+ * passed as single string or in the
175
+ * values of the given array
176
+ * @param bool $namesAsKeys If set to true, the option names
177
+ * should be passed in the keys of the
178
+ * required options array
179
+ *
180
+ * @throws MissingOptionsException If a required option is missing
181
+ *
182
+ * @since 2.6
183
+ */
184
+ public static function validateRequired (array $ options , $ requiredOptions , $ namesAsKeys = false )
185
+ {
186
+ $ requiredOptions = (array ) $ requiredOptions ;
187
+
188
+ if (!$ namesAsKeys ) {
189
+ $ requiredOptions = array_flip ($ requiredOptions );
190
+ }
191
+
192
+ $ diff = array_diff_key ($ requiredOptions , $ options );
193
+
194
+ if (count ($ diff ) > 0 ) {
195
+ ksort ($ diff );
196
+
197
+ throw new MissingOptionsException (sprintf (
198
+ count ($ diff ) > 1 ? 'The required options "%s" are missing. ' : 'The required option "%s" is missing. ' ,
199
+ implode ('", " ' , array_keys ($ diff ))
200
+ ));
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Validates that the given options match the accepted types and
206
+ * throws an exception otherwise.
207
+ *
208
+ * Accepted type names are any types for which a native "is_*()" function
209
+ * exists. For example, "int" is an acceptable type name and will be checked
210
+ * with the "is_int()" function.
211
+ *
212
+ * Types may also be passed as closures which return true or false.
213
+ *
214
+ * @param array $options A list of option names and values
215
+ * @param array $acceptedTypes A mapping of option names to accepted option
216
+ * types. The types may be given as
217
+ * string/closure or as array of strings/closures
218
+ *
219
+ * @throws InvalidOptionsException If any of the types does not match the
220
+ * accepted types of the option
221
+ *
222
+ * @since 2.6
223
+ */
224
+ public static function validateTypes (array $ options , array $ acceptedTypes )
225
+ {
226
+ foreach ($ acceptedTypes as $ option => $ optionTypes ) {
227
+ if (!array_key_exists ($ option , $ options )) {
228
+ continue ;
229
+ }
230
+
231
+ $ value = $ options [$ option ];
232
+ $ optionTypes = (array ) $ optionTypes ;
233
+
234
+ foreach ($ optionTypes as $ type ) {
235
+ $ isFunction = 'is_ ' .$ type ;
236
+
237
+ if (function_exists ($ isFunction ) && $ isFunction ($ value )) {
238
+ continue 2 ;
239
+ } elseif ($ value instanceof $ type ) {
240
+ continue 2 ;
241
+ }
242
+ }
243
+
244
+ $ printableValue = is_object ($ value )
245
+ ? get_class ($ value )
246
+ : (is_array ($ value )
247
+ ? 'Array '
248
+ : (string ) $ value );
249
+
250
+ throw new InvalidOptionsException (sprintf (
251
+ 'The option "%s" with value "%s" is expected to be of type "%s" ' ,
252
+ $ option ,
253
+ $ printableValue ,
254
+ implode ('", " ' , $ optionTypes )
255
+ ));
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Validates that the given option values match the accepted values and
261
+ * throws an exception otherwise.
262
+ *
263
+ * @param array $options A list of option names and values
264
+ * @param array $acceptedValues A mapping of option names to accepted option
265
+ * values. The option values must be given as
266
+ * arrays
267
+ *
268
+ * @throws InvalidOptionsException If any of the values does not match the
269
+ * accepted values of the option
270
+ *
271
+ * @since 2.6
272
+ */
273
+ public static function validateValues (array $ options , array $ acceptedValues )
274
+ {
275
+ foreach ($ acceptedValues as $ option => $ optionValues ) {
276
+ if (array_key_exists ($ option , $ options )) {
277
+ if (is_array ($ optionValues ) && !in_array ($ options [$ option ], $ optionValues , true )) {
278
+ throw new InvalidOptionsException (sprintf ('The option "%s" has the value "%s", but is expected to be one of "%s" ' , $ option , $ options [$ option ], implode ('", " ' , $ optionValues )));
279
+ }
280
+
281
+ if (is_callable ($ optionValues ) && !call_user_func ($ optionValues , $ options [$ option ])) {
282
+ throw new InvalidOptionsException (sprintf ('The option "%s" has the value "%s", which it is not valid ' , $ option , $ options [$ option ]));
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Constructs a new object with a set of default options.
290
+ *
291
+ * @param array $options A list of option names and values
292
+ */
293
+ public function __construct (array $ options = array ())
294
+ {
295
+ foreach ($ options as $ option => $ value ) {
296
+ $ this ->set ($ option , $ value );
297
+ }
298
+ }
299
+
59
300
/**
60
301
* Sets the value of a given option.
61
302
*
@@ -179,8 +420,10 @@ public function overload($option, $value)
179
420
180
421
// If an option is a closure that should be evaluated lazily, store it
181
422
// in the "lazy" property.
182
- if ($ value instanceof \Closure) {
183
- $ reflClosure = new \ReflectionFunction ($ value );
423
+ if (is_callable ($ value )) {
424
+ $ reflClosure = is_array ($ value )
425
+ ? new \ReflectionMethod ($ value [0 ], $ value [1 ])
426
+ : new \ReflectionFunction ($ value );
184
427
$ params = $ reflClosure ->getParameters ();
185
428
186
429
if (isset ($ params [0 ]) && null !== ($ class = $ params [0 ]->getClass ()) && __CLASS__ === $ class ->name ) {
@@ -229,11 +472,11 @@ public function get($option)
229
472
}
230
473
231
474
if (isset ($ this ->lazy [$ option ])) {
232
- $ this ->resolve ($ option );
475
+ $ this ->resolveOption ($ option );
233
476
}
234
477
235
478
if (isset ($ this ->normalizers [$ option ])) {
236
- $ this ->normalize ($ option );
479
+ $ this ->normalizeOption ($ option );
237
480
}
238
481
239
482
return $ this ->options [$ option ];
@@ -306,13 +549,13 @@ public function all()
306
549
// Double check, in case the option has already been resolved
307
550
// by cascade in the previous cycles
308
551
if (isset ($ this ->lazy [$ option ])) {
309
- $ this ->resolve ($ option );
552
+ $ this ->resolveOption ($ option );
310
553
}
311
554
}
312
555
313
556
foreach ($ this ->normalizers as $ option => $ normalizer ) {
314
557
if (isset ($ this ->normalizers [$ option ])) {
315
- $ this ->normalize ($ option );
558
+ $ this ->normalizeOption ($ option );
316
559
}
317
560
}
318
561
@@ -444,7 +687,7 @@ public function count()
444
687
* @throws OptionDefinitionException If the option has a cyclic dependency
445
688
* on another option.
446
689
*/
447
- private function resolve ($ option )
690
+ private function resolveOption ($ option )
448
691
{
449
692
// The code duplication with normalize() exists for performance
450
693
// reasons, in order to save a method call.
@@ -464,7 +707,7 @@ private function resolve($option)
464
707
465
708
$ this ->lock [$ option ] = true ;
466
709
foreach ($ this ->lazy [$ option ] as $ closure ) {
467
- $ this ->options [$ option ] = $ closure( $ this , $ this ->options [$ option ]);
710
+ $ this ->options [$ option ] = call_user_func ( $ closure, $ this , $ this ->options [$ option ]);
468
711
}
469
712
unset($ this ->lock [$ option ]);
470
713
@@ -482,7 +725,7 @@ private function resolve($option)
482
725
* @throws OptionDefinitionException If the option has a cyclic dependency
483
726
* on another option.
484
727
*/
485
- private function normalize ($ option )
728
+ private function normalizeOption ($ option )
486
729
{
487
730
// The code duplication with resolve() exists for performance
488
731
// reasons, in order to save a method call.
0 commit comments