8000 [OptionsResolver] Added a light-weight, low-level API for basic optio… · symfony/symfony@9066025 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9066025

Browse files
committed
[OptionsResolver] Added a light-weight, low-level API for basic option resolving
1 parent e86fe91 commit 9066025

File tree

7 files changed

+1066
-435
lines changed

7 files changed

+1066
-435
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\OptionsResolver\Exception;
13+
14+
/**
15+
* Thrown when an argument is invalid.
16+
*
17+
* @author Bernhard Schussek <bschussek@gmail.com>
18+
*/
19+
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
20+
{
21+
}

src/Symfony/Component/OptionsResolver/Options.php

Lines changed: 252 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace 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;
1417
use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
1518

1619
/**
@@ -56,6 +59,244 @@ class Options implements \ArrayAccess, \Iterator, \Countable
5659
*/
5760
priva 6D40 te $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::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+
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
}
311554
}
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

Comments
 (0)
0