]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
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; | |
13 | ||
14 | use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException; | |
15 | use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; | |
16 | use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; | |
17 | ||
18 | /** | |
19 | * Helper for merging default and concrete option values. | |
20 | * | |
21 | * @author Bernhard Schussek <bschussek@gmail.com> | |
22 | * @author Tobias Schultze <http://tobion.de> | |
23 | */ | |
24 | class OptionsResolver implements OptionsResolverInterface | |
25 | { | |
26 | /** | |
27 | * The default option values. | |
28 | * @var Options | |
29 | */ | |
30 | private $defaultOptions; | |
31 | ||
32 | /** | |
33 | * The options known by the resolver. | |
34 | * @var array | |
35 | */ | |
36 | private $knownOptions = array(); | |
37 | ||
38 | /** | |
39 | * The options without defaults that are required to be passed to resolve(). | |
40 | * @var array | |
41 | */ | |
42 | private $requiredOptions = array(); | |
43 | ||
44 | /** | |
45 | * A list of accepted values for each option. | |
46 | * @var array | |
47 | */ | |
48 | private $allowedValues = array(); | |
49 | ||
50 | /** | |
51 | * A list of accepted types for each option. | |
52 | * @var array | |
53 | */ | |
54 | private $allowedTypes = array(); | |
55 | ||
56 | /** | |
57 | * Creates a new instance. | |
58 | */ | |
59 | public function __construct() | |
60 | { | |
61 | $this->defaultOptions = new Options(); | |
62 | } | |
63 | ||
64 | /** | |
65 | * Clones the resolver. | |
66 | */ | |
67 | public function __clone() | |
68 | { | |
69 | $this->defaultOptions = clone $this->defaultOptions; | |
70 | } | |
71 | ||
72 | /** | |
73 | * {@inheritdoc} | |
74 | */ | |
75 | public function setDefaults(array $defaultValues) | |
76 | { | |
77 | foreach ($defaultValues as $option => $value) { | |
78 | $this->defaultOptions->overload($option, $value); | |
79 | $this->knownOptions[$option] = true; | |
80 | unset($this->requiredOptions[$option]); | |
81 | } | |
82 | ||
83 | return $this; | |
84 | } | |
85 | ||
86 | /** | |
87 | * {@inheritdoc} | |
88 | */ | |
89 | public function replaceDefaults(array $defaultValues) | |
90 | { | |
91 | foreach ($defaultValues as $option => $value) { | |
92 | $this->defaultOptions->set($option, $value); | |
93 | $this->knownOptions[$option] = true; | |
94 | unset($this->requiredOptions[$option]); | |
95 | } | |
96 | ||
97 | return $this; | |
98 | } | |
99 | ||
100 | /** | |
101 | * {@inheritdoc} | |
102 | */ | |
103 | public function setOptional(array $optionNames) | |
104 | { | |
105 | foreach ($optionNames as $key => $option) { | |
106 | if (!is_int($key)) { | |
107 | throw new OptionDefinitionException('You should not pass default values to setOptional()'); | |
108 | } | |
109 | ||
110 | $this->knownOptions[$option] = true; | |
111 | } | |
112 | ||
113 | return $this; | |
114 | } | |
115 | ||
116 | /** | |
117 | * {@inheritdoc} | |
118 | */ | |
119 | public function setRequired(array $optionNames) | |
120 | { | |
121 | foreach ($optionNames as $key => $option) { | |
122 | if (!is_int($key)) { | |
123 | throw new OptionDefinitionException('You should not pass default values to setRequired()'); | |
124 | } | |
125 | ||
126 | $this->knownOptions[$option] = true; | |
127 | // set as required if no default has been set already | |
128 | if (!isset($this->defaultOptions[$option])) { | |
129 | $this->requiredOptions[$option] = true; | |
130 | } | |
131 | } | |
132 | ||
133 | return $this; | |
134 | } | |
135 | ||
136 | /** | |
137 | * {@inheritdoc} | |
138 | */ | |
139 | public function setAllowedValues(array $allowedValues) | |
140 | { | |
141 | $this->validateOptionsExistence($allowedValues); | |
142 | ||
143 | $this->allowedValues = array_replace($this->allowedValues, $allowedValues); | |
144 | ||
145 | return $this; | |
146 | } | |
147 | ||
148 | /** | |
149 | * {@inheritdoc} | |
150 | */ | |
151 | public function addAllowedValues(array $allowedValues) | |
152 | { | |
153 | $this->validateOptionsExistence($allowedValues); | |
154 | ||
155 | $this->allowedValues = array_merge_recursive($this->allowedValues, $allowedValues); | |
156 | ||
157 | return $this; | |
158 | } | |
159 | ||
160 | /** | |
161 | * {@inheritdoc} | |
162 | */ | |
163 | public function setAllowedTypes(array $allowedTypes) | |
164 | { | |
165 | $this->validateOptionsExistence($allowedTypes); | |
166 | ||
167 | $this->allowedTypes = array_replace($this->allowedTypes, $allowedTypes); | |
168 | ||
169 | return $this; | |
170 | } | |
171 | ||
172 | /** | |
173 | * {@inheritdoc} | |
174 | */ | |
175 | public function addAllowedTypes(array $allowedTypes) | |
176 | { | |
177 | $this->validateOptionsExistence($allowedTypes); | |
178 | ||
179 | $this->allowedTypes = array_merge_recursive($this->allowedTypes, $allowedTypes); | |
180 | ||
181 | return $this; | |
182 | } | |
183 | ||
184 | /** | |
185 | * {@inheritdoc} | |
186 | */ | |
187 | public function setNormalizers(array $normalizers) | |
188 | { | |
189 | $this->validateOptionsExistence($normalizers); | |
190 | ||
191 | foreach ($normalizers as $option => $normalizer) { | |
192 | $this->defaultOptions->setNormalizer($option, $normalizer); | |
193 | } | |
194 | ||
195 | return $this; | |
196 | } | |
197 | ||
198 | /** | |
199 | * {@inheritdoc} | |
200 | */ | |
201 | public function isKnown($option) | |
202 | { | |
203 | return isset($this->knownOptions[$option]); | |
204 | } | |
205 | ||
206 | /** | |
207 | * {@inheritdoc} | |
208 | */ | |
209 | public function isRequired($option) | |
210 | { | |
211 | return isset($this->requiredOptions[$option]); | |
212 | } | |
213 | ||
214 | /** | |
215 | * {@inheritdoc} | |
216 | */ | |
217 | public function resolve(array $options = array()) | |
218 | { | |
219 | $this->validateOptionsExistence($options); | |
220 | $this->validateOptionsCompleteness($options); | |
221 | ||
222 | // Make sure this method can be called multiple times | |
223 | $combinedOptions = clone $this->defaultOptions; | |
224 | ||
225 | // Override options set by the user | |
226 | foreach ($options as $option => $value) { | |
227 | $combinedOptions->set($option, $value); | |
228 | } | |
229 | ||
230 | // Resolve options | |
231 | $resolvedOptions = $combinedOptions->all(); | |
232 | ||
233 | $this->validateOptionValues($resolvedOptions); | |
234 | $this->validateOptionTypes($resolvedOptions); | |
235 | ||
236 | return $resolvedOptions; | |
237 | } | |
238 | ||
239 | /** | |
240 | * Validates that the given option names exist and throws an exception | |
241 | * otherwise. | |
242 | * | |
243 | * @param array $options An list of option names as keys. | |
244 | * | |
245 | * @throws InvalidOptionsException If any of the options has not been defined. | |
246 | */ | |
247 | private function validateOptionsExistence(array $options) | |
248 | { | |
249 | $diff = array_diff_key($options, $this->knownOptions); | |
250 | ||
251 | if (count($diff) > 0) { | |
252 | ksort($this->knownOptions); | |
253 | ksort($diff); | |
254 | ||
255 | throw new InvalidOptionsException(sprintf( | |
256 | (count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Known options are: "%s"', | |
257 | implode('", "', array_keys($diff)), | |
258 | implode('", "', array_keys($this->knownOptions)) | |
259 | )); | |
260 | } | |
261 | } | |
262 | ||
263 | /** | |
264 | * Validates that all required options are given and throws an exception | |
265 | * otherwise. | |
266 | * | |
267 | * @param array $options An list of option names as keys. | |
268 | * | |
269 | * @throws MissingOptionsException If a required option is missing. | |
270 | */ | |
271 | private function validateOptionsCompleteness(array $options) | |
272 | { | |
273 | $diff = array_diff_key($this->requiredOptions, $options); | |
274 | ||
275 | if (count($diff) > 0) { | |
276 | ksort($diff); | |
277 | ||
278 | throw new MissingOptionsException(sprintf( | |
279 | count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', | |
280 | implode('", "', array_keys($diff)) | |
281 | )); | |
282 | } | |
283 | } | |
284 | ||
285 | /** | |
286 | * Validates that the given option values match the allowed values and | |
287 | * throws an exception otherwise. | |
288 | * | |
289 | * @param array $options A list of option values. | |
290 | * | |
291 | * @throws InvalidOptionsException If any of the values does not match the | |
292 | * allowed values of the option. | |
293 | */ | |
294 | private function validateOptionValues(array $options) | |
295 | { | |
296 | foreach ($this->allowedValues as $option => $allowedValues) { | |
297 | if (isset($options[$option]) && !in_array($options[$option], $allowedValues, true)) { | |
298 | throw new InvalidOptionsException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues))); | |
299 | } | |
300 | } | |
301 | } | |
302 | ||
303 | /** | |
304 | * Validates that the given options match the allowed types and | |
305 | * throws an exception otherwise. | |
306 | * | |
307 | * @param array $options A list of options. | |
308 | * | |
309 | * @throws InvalidOptionsException If any of the types does not match the | |
310 | * allowed types of the option. | |
311 | */ | |
312 | private function validateOptionTypes(array $options) | |
313 | { | |
314 | foreach ($this->allowedTypes as $option => $allowedTypes) { | |
315 | if (!array_key_exists($option, $options)) { | |
316 | continue; | |
317 | } | |
318 | ||
319 | $value = $options[$option]; | |
320 | $allowedTypes = (array) $allowedTypes; | |
321 | ||
322 | foreach ($allowedTypes as $type) { | |
323 | $isFunction = 'is_'.$type; | |
324 | ||
325 | if (function_exists($isFunction) && $isFunction($value)) { | |
326 | continue 2; | |
327 | } elseif ($value instanceof $type) { | |
328 | continue 2; | |
329 | } | |
330 | } | |
331 | ||
332 | $printableValue = is_object($value) | |
333 | ? get_class($value) | |
334 | : (is_array($value) | |
335 | ? 'Array' | |
336 | : (string) $value); | |
337 | ||
338 | throw new InvalidOptionsException(sprintf( | |
339 | 'The option "%s" with value "%s" is expected to be of type "%s"', | |
340 | $option, | |
341 | $printableValue, | |
342 | implode('", "', $allowedTypes) | |
343 | )); | |
344 | } | |
345 | } | |
346 | } |