1111
1212namespace Symfony \Component \OptionsResolver ;
1313
14+ use Symfony \Component \OptionsResolver \Exception \InvalidArgumentException ;
15+ use Symfony \Component \OptionsResolver \Exception \InvalidOptionsException ;
16+ use Symfony \Component \OptionsResolver \Exception \MissingOptionsException ;
1417use Symfony \Component \OptionsResolver \Exception \OptionDefinitionException ;
1518
1619/**
@@ -56,6 +59,244 @@ class Options implements \ArrayAccess, \Iterator, \Countable
5659 */
5760 private $ reading = false ;
5861
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 ::validateValues ($ resolvedOptions , $ defaults ->allowedValues );
118+ static ::validateTypes ($ resolvedOptions , $ defaults ->allowedTypes );
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+
59300 /**
60301 * Sets the value of a given option.
61302 *
@@ -179,8 +420,10 @@ public function overload($option, $value)
179420
180421 // If an option is a closure that should be evaluated lazily, store it
181422 // 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 );
184427 $ params = $ reflClosure ->getParameters ();
185428
186429 if (isset ($ params [0 ]) && null !== ($ class = $ params [0 ]->getClass ()) && __CLASS__ === $ class ->name ) {
@@ -229,11 +472,11 @@ public function get($option)
229472 }
230473
231474 if (isset ($ this ->lazy [$ option ])) {
232- $ this ->resolve ($ option );
475+ $ this ->resolveOption ($ option );
233476 }
234477
235478 if (isset ($ this ->normalizers [$ option ])) {
236- $ this ->normalize ($ option );
479+ $ this ->normalizeOption ($ option );
237480 }
238481
239482 return $ this ->options [$ option ];
@@ -306,13 +549,13 @@ public function all()
306549 // Double check, in case the option has already been resolved
307550 // by cascade in the previous cycles
308551 if (isset ($ this ->lazy [$ option ])) {
309- $ this ->resolve ($ option );
552+ $ this ->resolveOption ($ option );
310553 }
311
4B72
554 }
312555
313556 foreach ($ this ->normalizers as $ option => $ normalizer ) {
314557 if (isset ($ this ->normalizers [$ option ])) {
315- $ this ->normalize ($ option );
558+ $ this ->normalizeOption ($ option );
316559 }
317560 }
318561
@@ -444,7 +687,7 @@ public function count()
444687 * @throws OptionDefinitionException If the option has a cyclic dependency
445688 * on another option.
446689 */
447- private function resolve ($ option )
690+ private function resolveOption ($ option )
448691 {
449692 // The code duplication with normalize() exists for performance
450693 // reasons, in order to save a method call.
@@ -464,7 +707,7 @@ private function resolve($option)
464707
465708 $ this ->lock [$ option ] = true ;
466709 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 ]);
468711 }
469712 unset($ this ->lock [$ option ]);
470713
@@ -482,7 +725,7 @@ private function resolve($option)
482725 * @throws OptionDefinitionException If the option has a cyclic dependency
483726 * on another option.
484727 */
485- private function normalize ($ option )
728+ private function normalizeOption ($ option )
486729 {
487730 // The code duplication with resolve() exists for performance
488731 // reasons, in order to save a method call.
0 commit comments