|
11 | 11 |
|
12 | 12 | namespace Symfony\Component\OptionsResolver;
|
13 | 13 |
|
| 14 | +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; |
| 15 | +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; |
14 | 16 | use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
|
15 | 17 |
|
16 | 18 | /**
|
@@ -56,6 +58,179 @@ class Options implements \ArrayAccess, \Iterator, \Countable
|
56 | 58 | */
|
57 | 59 | private $reading = false;
|
58 | 60 |
|
| 61 | + /** |
| 62 | + * Merges options with an array of default values and throws an exception if |
| 63 | + * any of the options does not exist. |
| 64 | + * |
| 65 | + * @param array $options A list of option names and values |
| 66 | + * @param array $defaults The accepted options and their default values |
| 67 | + * |
| 68 | + * @return array The merged and validated options |
| 69 | + * |
| 70 | + * @throws InvalidOptionsException If any of the options is not present in |
| 71 | + * the defaults array |
| 72 | + */ |
| 73 | + public static function resolve(array $options, array $defaults) |
| 74 | + { |
| 75 | + static::accept($options, $defaults, true); |
| 76 | + |
| 77 | + return array_replace($defaults, $options); |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Validates that the given option names exist and throws an exception |
| 82 | + * otherwise. |
| 83 | + * |
| 84 | + * @param array $options A list of option names and values |
| 85 | + * @param string|array $acceptedOptions The accepted option(s), either passed |
| 86 | + * as single string or in the values of |
| 87 | + * the given array |
| 88 | + * @param bool $optionsAsKeys If set to true, the option names |
| 89 | + * should be passed in the keys of the |
| 90 | + * accepted options array |
| 91 | + * |
| 92 | + * @throws InvalidOptionsException If any of the options is not present in |
| 93 | + * the accepted options |
| 94 | + */ |
| 95 | + public static function accept(array $options, $acceptedOptions, $optionsAsKeys = false) |
| 96 | + { |
| 97 | + $acceptedOptions = (array) $acceptedOptions; |
| 98 | + |
| 99 | + if (!$optionsAsKeys) { |
| 100 | + $acceptedOptions = array_flip($acceptedOptions); |
| 101 | + } |
| 102 | + |
| 103 | + $diff = array_diff_key($options, $acceptedOptions); |
| 104 | + |
| 105 | + if (count($diff) > 0) { |
| 106 | + ksort($acceptedOptions); |
| 107 | + ksort($diff); |
| 108 | + |
| 109 | + throw new InvalidOptionsException(sprintf( |
| 110 | + (count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Known options are: "%s"', |
| 111 | + implode('", "', array_keys($diff)), |
| 112 | + implode('", "', array_keys($acceptedOptions)) |
| 113 | + )); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Validates that the required options are given and throws an exception |
| 119 | + * otherwise. |
| 120 | + * |
| 121 | + * The option names may be any strings that don't consist exclusively of |
| 122 | + * digits. For example, "case1" is a valid option name, "1" is not. |
| 123 | + * |
| 124 | + * @param array $options A list of option names and values |
| 125 | + * @param string|array $requiredOptions The required option(s), either |
| 126 | + * passed as single string or in the |
| 127 | + * values of the given array |
| 128 | + * @param bool $optionsAsKeys If set to true, the option names |
| 129 | + * should be passed in the keys of the |
| 130 | + * required options array |
| 131 | + * |
| 132 | + * @throws MissingOptionsException If a required option is missing |
| 133 | + */ |
| 134 | + public static function require_(array $options, $requiredOptions, $optionsAsKeys = false) |
| 135 | + { |
| 136 | + $requiredOptions = (array) $requiredOptions; |
| 137 | + |
| 138 | + if (!$optionsAsKeys) { |
| 139 | + $requiredOptions = array_flip($requiredOptions); |
| 140 | + } |
| 141 | + |
| 142 | + $diff = array_diff_key($requiredOptions, $options); |
| 143 | + |
| 144 | + if (count($diff) > 0) { |
| 145 | + ksort($diff); |
| 146 | + |
| 147 | + throw new MissingOptionsException(sprintf( |
| 148 | + count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', |
| 149 | + implode('", "', array_keys($diff)) |
| 150 | + )); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Validates that the given options match the accepted types and |
| 156 | + * throws an exception otherwise. |
| 157 | + * |
| 158 | + * Accepted type names are any types for which a native "is_*()" function |
| 159 | + * exists. For example, "int" is an acceptable type name and will be checked |
| 160 | + * with the "is_int()" function. |
| 161 | + * |
| 162 | + * Types may also be passed as closures which return true or false. |
| 163 | + * |
| 164 | + * @param array $options A list of option names and values |
| 165 | + * @param array $acceptedTypes A mapping of option names to accepted option |
| 166 | + * types. The types may be given as |
| 167 | + * string/closure or as array of strings/closures |
| 168 | + * |
| 169 | + * @throws InvalidOptionsException If any of the types does not match the |
| 170 | + * accepted types of the option |
| 171 | + */ |
| 172 | + public static function validateTypes(array $options, array $acceptedTypes) |
| 173 | + { |
| 174 | + foreach ($acceptedTypes as $option => $optionTypes) { |
| 175 | + if (!array_key_exists($option, $options)) { |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + $value = $options[$option]; |
| 180 | + $optionTypes = (array) $optionTypes; |
| 181 | + |
| 182 | + foreach ($optionTypes as $type) { |
| 183 | + $isFunction = 'is_'.$type; |
| 184 | + |
| 185 | + if (function_exists($isFunction) && $isFunction($value)) { |
| 186 | + continue 2; |
| 187 | + } elseif ($value instanceof $type) { |
| 188 | + continue 2; |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + $printableValue = is_object($value) |
| 193 | + ? get_class($value) |
| 194 | + : (is_array($value) |
| 195 | + ? 'Array' |
| 196 | + : (string) $value); |
| 197 | + |
| 198 | + throw new InvalidOptionsException(sprintf( |
| 199 | + 'The option "%s" with value "%s" is expected to be of type "%s"', |
| 200 | + $option, |
| 201 | + $printableValue, |
| 202 | + implode('", "', $optionTypes) |
| 203 | + )); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + /** |
| 208 | + * Validates that the given option values match the accepted values and |
| 209 | + * throws an exception otherwise. |
| 210 | + * |
| 211 | + * @param array $options A list of option names and values |
| 212 | + * @param array $acceptedValues A mapping of option names to accepted option |
| 213 | + * values. The option values must be given as |
| 214 | + * arrays |
| 215 | + * |
| 216 | + * @throws InvalidOptionsException If any of the values does not match the |
| 217 | + * accepted values of the option |
| 218 | + */ |
| 219 | + public static function validateValues(array $options, array $acceptedValues) |
| 220
+ { |
| 221 | + foreach ($acceptedValues as $option => $optionValues) { |
| 222 | + if (isset($options[$option])) { |
| 223 | + if (is_array($optionValues) && !in_array($options[$option], $optionValues, true)) { |
| 224 | + throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $optionValues))); |
| 225 | + } |
| 226 | + |
| 227 | + if (is_callable($optionValues) && !call_user_func($optionValues, $options[$option])) { |
| 228 | + throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", which it is not valid', $option, $options[$option])); |
| 229 | + } |
| 230 | + } |
| 231 | + } |
| 232 | + } |
| 233 | + |
59 | 234 | /**
|
60 | 235 | * Sets the value of a given option.
|
61 | 236 | *
|
@@ -229,11 +404,11 @@ public function get($option)
|
229 | 404 | }
|
230 | 405 |
|
231 | 406 | if (isset($this->lazy[$option])) {
|
232 |
| - $this->resolve($option); |
| 407 | + $this->resolveOption($option); |
233 | 408 | }
|
234 | 409 |
|
235 | 410 | if (isset($this->normalizers[$option])) {
|
236 |
| - $this->normalize($option); |
| 411 | + $this->normalizeOption($option); |
237 | 412 | }
|
238 | 413 |
|
239 | 414 | return $this->options[$option];
|
@@ -306,13 +481,13 @@ public function all()
|
306 | 481 | // Double check, in case the option has already been resolved
|
307 | 482 | // by cascade in the previous cycles
|
308 | 483 | if (isset($this->lazy[$option])) {
|
309 |
| - $this->resolve($option); |
| 484 | + $this->resolveOption($option); |
310 | 485 | }
|
311 | 486 | }
|
312 | 487 |
|
313 | 488 | foreach ($this->normalizers as $option => $normalizer) {
|
314 | 489 | if (isset($this->normalizers[$option])) {
|
315 |
| - $this->normalize($option); |
| 490 | + $this->normalizeOption($option); |
316 | 491 | }
|
317 | 492 | }
|
318 | 493 |
|
@@ -444,7 +619,7 @@ public function count()
|
444 | 619 | * @throws OptionDefinitionException If the option has a cyclic dependency
|
445 | 620 | * on another option.
|
446 | 621 | */
|
447 |
| - private function resolve($option) |
| 622 | + private function resolveOption($option) |
448 | 623 | {
|
449 | 624 | // The code duplication with normalize() exists for performance
|
450 | 625 | // reasons, in order to save a method call.
|
@@ -482,7 +657,7 @@ private function resolve($option)
|
482 | 657 | * @throws OptionDefinitionException If the option has a cyclic dependency
|
483 | 658 | * on another option.
|
484 | 659 | */
|
485 |
| - private function normalize($option) |
| 660 | + private function normalizeOption($option) |
486 | 661 | {
|
487 | 662 | // The code duplication with resolve() exists for performance
|
488 | 663 | // reasons, in order to save a method call.
|
|
0 commit comments