|
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 | private $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::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 | + * @pa
F987
ram 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 | *
|
@@ -229,11 +470,11 @@ public function get($option)
|
229 | 470 | }
|
230 | 471 |
|
231 | 472 | if (isset($this->lazy[$option])) {
|
232 |
| - $this->resolve($option); |
| 473 | + $this->resolveOption($option); |
233 | 474 | }
|
234 | 475 |
|
235 | 476 | if (isset($this->normalizers[$option])) {
|
236 |
| - $this->normalize($option); |
| 477 | + $this->normalizeOption($option); |
237 | 478 | }
|
238 | 479 |
|
239 | 480 | return $this->options[$option];
|
@@ -306,13 +547,13 @@ public function all()
|
306 | 547 | // Double check, in case the option has already been resolved
|
307 | 548 | // by cascade in the previous cycles
|
308 | 549 | if (isset($this->lazy[$option])) {
|
309 |
| - $this->resolve($option); |
| 550 | + $this->resolveOption($option); |
310 | 551 | }
|
311 | 552 | }
|
312 | 553 |
|
313 | 554 | foreach ($this->normalizers as $option => $normalizer) {
|
314 | 555 | if (isset($this->normalizers[$option])) {
|
315 |
| - $this->normalize($option); |
| 556 | + $this->normalizeOption($option); |
316 | 557 | }
|
317 | 558 | }
|
318 | 559 |
|
@@ -444,7 +685,7 @@ public function count()
|
444 | 685 | * @throws OptionDefinitionException If the option has a cyclic dependency
|
445 | 686 | * on another option.
|
446 | 687 | */
|
447 |
| - private function resolve($option) |
| 688 | + private function resolveOption($option) |
448 | 689 | {
|
449 | 690 | // The code duplication with normalize() exists for performance
|
450 | 691 | // reasons, in order to save a method call.
|
@@ -482,7 +723,7 @@ private function resolve($option)
|
482 | 723 | * @throws OptionDefinitionException If the option has a cyclic dependency
|
483 | 724 | * on another option.
|
484 | 725 | */
|
485 |
| - private function normalize($option) |
| 726 | + private function normalizeOption($option) |
486 | 727 | {
|
487 | 728 | // The code duplication with resolve() exists for performance
|
488 | 729 | // reasons, in order to save a method call.
|
|
0 commit comments