Completed
Pull Request — master (#2208)
by Christian
02:31
created

Configuration::testExceptionExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of the FOSRestBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.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 FOS\RestBundle\DependencyInjection;
13
14
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
15
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
16
use Symfony\Component\Config\Definition\ConfigurationInterface;
17
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\OptionsResolver\OptionsResolver;
20
use Symfony\Component\Serializer\Encoder\XmlEncoder;
21
22
/**
23
 * This class contains the configuration information for the bundle.
24
 *
25
 * This information is solely responsible for how the different configuration
26
 * sections are normalized, and merged.
27
 *
28
 * @author Lukas Kahwe Smith <[email protected]>
29
 *
30
 * @internal
31
 */
32
final class Configuration implements ConfigurationInterface
33
{
34
    private $debug;
35
36 59
    public function __construct(bool $debug)
37
    {
38 59
        $this->debug = $debug;
39 59
    }
40
41 58
    public function getConfigTreeBuilder(): TreeBuilder
42
    {
43 58
        $treeBuilder = new TreeBuilder('fos_rest');
44
45 58
        $rootNode = $treeBuilder->getRootNode();
46
47
        $rootNode
48 58
            ->children()
49 58
                ->scalarNode('disable_csrf_role')->defaultNull()->end()
50 58
                ->arrayNode('access_denied_listener')
51 58
                    ->canBeEnabled()
52 58
                    ->beforeNormalization()
53 View Code Duplication
                        ->ifArray()->then(function ($v) {
54
                            if (!empty($v) && empty($v['formats'])) {
55
                                unset($v['enabled']);
56
                                $v = ['enabled' => true, 'formats' => $v];
57
                            }
58
59
                            return $v;
60 58
                        })
61 58
                    ->end()
62 58
                    ->fixXmlConfig('format', 'formats')
63 58
                    ->children()
64 58
                        ->scalarNode('service')->defaultNull()->end()
65 58
                        ->arrayNode('formats')
66 58
                            ->useAttributeAsKey('name')
67 58
                            ->prototype('boolean')->end()
68 58
                        ->end()
69 58
                    ->end()
70 58
                ->end()
71 58
                ->scalarNode('unauthorized_challenge')->defaultNull()->end()
72 58
                ->arrayNode('param_fetcher_listener')
73 58
                    ->beforeNormalization()
74 58
                        ->ifString()
75 View Code Duplication
                        ->then(function ($v) {
76 1
                            return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
77 58
                        })
78 58
                    ->end()
79 58
                    ->canBeEnabled()
80 58
                    ->children()
81 58
                        ->booleanNode('force')->defaultFalse()->end()
82 58
                        ->scalarNode('service')->defaultNull()->end()
83 58
                    ->end()
84 58
                ->end()
85 58
                ->scalarNode('cache_dir')->cannotBeEmpty()->defaultValue('%kernel.cache_dir%/fos_rest')->end()
86 58
                ->arrayNode('allowed_methods_listener')
87 58
                    ->canBeEnabled()
88 58
                    ->children()
89 58
                        ->scalarNode('service')->defaultNull()->end()
90 58
                    ->end()
91 58
                ->end()
92 58
                ->booleanNode('routing_loader')
93 58
                    ->defaultValue(false)
94 58
                    ->validate()
95 58
                        ->ifTrue()
96 58
                        ->thenInvalid('only "false" is supported')
97 58
                    ->end()
98 58
                ->end()
99 58
                ->arrayNode('body_converter')
100 58
                    ->canBeEnabled()
101 58
                    ->children()
102 58
                        ->scalarNode('validate')
103 58
                            ->defaultFalse()
104 58
                            ->beforeNormalization()
105 58
                                ->ifTrue()
106
                                ->then(function ($value) {
107 1
                                    if (!class_exists(OptionsResolver::class)) {
108
                                        throw new InvalidConfigurationException("'body_converter.validate: true' requires OptionsResolver component installation ( composer require symfony/options-resolver )");
109
                                    }
110
111 1
                                    return $value;
112 58
                                })
113 58
                            ->end()
114 58
                        ->end()
115 58
                        ->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end()
116 58
                    ->end()
117 58
                ->end()
118 58
                ->arrayNode('service')
119 58
                    ->addDefaultsIfNotSet()
120 58
                    ->children()
121 58
                        ->scalarNode('serializer')->defaultNull()->end()
122 58
                        ->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end()
123 58
                        ->scalarNode('validator')->defaultValue('validator')->end()
124 58
                    ->end()
125 58
                ->end()
126 58
                ->arrayNode('serializer')
127 58
                    ->addDefaultsIfNotSet()
128 58
                    ->children()
129 58
                        ->scalarNode('version')->defaultNull()->end()
130 58
                        ->arrayNode('groups')
131 58
                            ->prototype('scalar')->end()
132 58
                        ->end()
133 58
                        ->booleanNode('serialize_null')->defaultFalse()->end()
134 58
                    ->end()
135 58
                ->end()
136 58
                ->arrayNode('zone')
137 58
                    ->cannotBeOverwritten()
138 58
                    ->prototype('array')
139 58
                    ->fixXmlConfig('ip')
140 58
                    ->children()
141 58
                        ->scalarNode('path')
142 58
                            ->defaultNull()
143 58
                            ->info('use the urldecoded format')
144 58
                            ->example('^/path to resource/')
145 58
                        ->end()
146 58
                        ->scalarNode('host')->defaultNull()->end()
147 58
                        ->arrayNode('methods')
148
                            ->beforeNormalization()->ifString()->then(function ($v) {
149
                                return preg_split('/\s*,\s*/', $v);
150 58
                            })->end()
151 58
                            ->prototype('scalar')->end()
152 58
                        ->end()
153 58
                        ->arrayNode('ips')
154
                            ->beforeNormalization()->ifString()->then(function ($v) {
155 1
                                return array($v);
156 58
                            })->end()
157 58
                            ->prototype('scalar')->end()
158 58
                        ->end()
159 58
                    ->end()
160 58
                ->end()
161 58
            ->end()
162 58
        ->end();
163
164 58
        $this->addViewSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
165 58
        $this->addExceptionSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
166 58
        $this->addBodyListenerSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
167 58
        $this->addFormatListenerSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
168 58
        $this->addVersioningSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
169
170 58
        return $treeBuilder;
171
    }
172
173 58
    private function addViewSection(ArrayNodeDefinition $rootNode): void
174
    {
175
        $rootNode
176 58
            ->children()
177 58
                ->arrayNode('view')
178 58
                    ->fixXmlConfig('format', 'formats')
179 58
                    ->fixXmlConfig('mime_type', 'mime_types')
180 58
                    ->fixXmlConfig('force_redirect', 'force_redirects')
181 58
                    ->addDefaultsIfNotSet()
182 58
                    ->children()
183 58
                        ->arrayNode('mime_types')
184 58
                            ->canBeEnabled()
185 58
                            ->beforeNormalization()
186 View Code Duplication
                                ->ifArray()->then(function ($v) {
187 1
                                    if (!empty($v) && empty($v['formats'])) {
188 1
                                        unset($v['enabled']);
189 1
                                        $v = ['enabled' => true, 'formats' => $v];
190
                                    }
191
192 1
                                    return $v;
193 58
                                })
194 58
                            ->end()
195 58
                            ->fixXmlConfig('format', 'formats')
196 58
                            ->children()
197 58
                                ->scalarNode('service')->defaultNull()->end()
198 58
                                ->arrayNode('formats')
199 58
                                    ->useAttributeAsKey('name')
200 58
                                    ->prototype('array')
201 58
                                        ->beforeNormalization()
202 58
                                            ->ifString()
203
                                            ->then(function ($v) { return array($v); })
204 58
                                        ->end()
205 58
                                        ->prototype('scalar')->end()
206 58
                                    ->end()
207 58
                                ->end()
208 58
                            ->end()
209 58
                        ->end()
210 58
                        ->arrayNode('formats')
211 58
                            ->useAttributeAsKey('name')
212 58
                            ->defaultValue(['json' => true, 'xml' => true])
213 58
                            ->prototype('boolean')->end()
214 58
                        ->end()
215 58
                        ->arrayNode('view_response_listener')
216 58
                            ->beforeNormalization()
217 58
                                ->ifString()
218 View Code Duplication
                                ->then(function ($v) {
219 4
                                    return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
220 58
                                })
221 58
                            ->end()
222 58
                            ->canBeEnabled()
223 58
                            ->children()
224 58
                                ->booleanNode('force')->defaultFalse()->end()
225 58
                                ->scalarNode('service')->defaultNull()->end()
226 58
                            ->end()
227 58
                        ->end()
228 58
                        ->scalarNode('failed_validation')->defaultValue(Response::HTTP_BAD_REQUEST)->end()
229 58
                        ->scalarNode('empty_content')->defaultValue(Response::HTTP_NO_CONTENT)->end()
230 58
                        ->booleanNode('serialize_null')->defaultFalse()->end()
231 58
                        ->arrayNode('jsonp_handler')
232 58
                            ->canBeUnset()
233 58
                            ->children()
234 58
                                ->scalarNode('callback_param')->defaultValue('callback')->end()
235 58
                                ->scalarNode('mime_type')->defaultValue('application/javascript+jsonp')->end()
236 58
                            ->end()
237 58
                        ->end()
238 58
                    ->end()
239 58
                ->end()
240 58
            ->end();
241 58
    }
242
243 58
    private function addBodyListenerSection(ArrayNodeDefinition $rootNode): void
244
    {
245 58
        $decodersDefaultValue = ['json' => 'fos_rest.decoder.json'];
246 58
        if (class_exists(XmlEncoder::class)) {
247 58
            $decodersDefaultValue['xml'] = 'fos_rest.decoder.xml';
248
        }
249
        $rootNode
250 58
            ->children()
251 58
                ->arrayNode('body_listener')
252 58
                    ->fixXmlConfig('decoder', 'decoders')
253 58
                    ->addDefaultsIfNotSet()
254 58
                    ->canBeUnset()
255 58
                    ->canBeEnabled()
256 58
                    ->children()
257 58
                        ->scalarNode('service')->defaultNull()->end()
258 58
                        ->scalarNode('default_format')->defaultNull()->end()
259 58
                        ->booleanNode('throw_exception_on_unsupported_content_type')
260 58
                            ->defaultFalse()
261 58
                        ->end()
262 58
                        ->arrayNode('decoders')
263 58
                            ->useAttributeAsKey('name')
264 58
                            ->defaultValue($decodersDefaultValue)
265 58
                            ->prototype('scalar')->end()
266 58
                        ->end()
267 58
                        ->arrayNode('array_normalizer')
268 58
                            ->addDefaultsIfNotSet()
269 58
                            ->beforeNormalization()
270
                                ->ifString()->then(function ($v) {
271 1
                                    return ['service' => $v];
272 58
                                })
273 58
                            ->end()
274 58
                            ->children()
275 58
                                ->scalarNode('service')->defaultNull()->end()
276 58
                                ->booleanNode('forms')->defaultFalse()->end()
277 58
                            ->end()
278 58
                        ->end()
279 58
                    ->end()
280 58
                ->end()
281 58
            ->end();
282 58
    }
283
284 58
    private function addFormatListenerSection(ArrayNodeDefinition $rootNode): void
285
    {
286
        $rootNode
287 58
            ->children()
288 58
                ->arrayNode('format_listener')
289 58
                    ->fixXmlConfig('rule', 'rules')
290 58
                    ->addDefaultsIfNotSet()
291 58
                    ->canBeUnset()
292 58
                    ->beforeNormalization()
293
                        ->ifTrue(function ($v) {
294
                            // check if we got an assoc array in rules
295 6
                            return isset($v['rules'])
296 6
                                && is_array($v['rules'])
297 6
                                && array_keys($v['rules']) !== range(0, count($v['rules']) - 1);
298 58
                        })
299
                        ->then(function ($v) {
300 1
                            $v['rules'] = [$v['rules']];
301
302 1
                            return $v;
303 58
                        })
304 58
                    ->end()
305 58
                    ->canBeEnabled()
306 58
                    ->children()
307 58
                        ->scalarNode('service')->defaultNull()->end()
308 58
                        ->arrayNode('rules')
309 58
                            ->performNoDeepMerging()
310 58
                            ->prototype('array')
311 58
                                ->fixXmlConfig('priority', 'priorities')
312 58
                                ->fixXmlConfig('attribute', 'attributes')
313 58
                                ->children()
314 58
                                    ->scalarNode('path')->defaultNull()->info('URL path info')->end()
315 58
                                    ->scalarNode('host')->defaultNull()->info('URL host name')->end()
316 58
                                    ->variableNode('methods')->defaultNull()->info('Method for URL')->end()
317 58
                                    ->arrayNode('attributes')
318 58
                                        ->useAttributeAsKey('name')
319 58
                                        ->prototype('variable')->end()
320 58
                                    ->end()
321 58
                                    ->booleanNode('stop')->defaultFalse()->end()
322 58
                                    ->booleanNode('prefer_extension')->defaultTrue()->end()
323 58
                                    ->scalarNode('fallback_format')->defaultValue('html')->end()
324 58
                                    ->arrayNode('priorities')
325
                                        ->beforeNormalization()->ifString()->then(function ($v) {
326
                                            return preg_split('/\s*,\s*/', $v);
327 58
                                        })->end()
328 58
                                        ->prototype('scalar')->end()
329 58
                                    ->end()
330 58
                                ->end()
331 58
                            ->end()
332 58
                        ->end()
333 58
                    ->end()
334 58
                ->end()
335 58
            ->end();
336 58
    }
337
338 58
    private function addVersioningSection(ArrayNodeDefinition $rootNode): void
339
    {
340
        $rootNode
341 58
        ->children()
342 58
            ->arrayNode('versioning')
343 58
                ->canBeEnabled()
344 58
                ->children()
345 58
                    ->scalarNode('default_version')->defaultNull()->end()
346 58
                    ->arrayNode('resolvers')
347 58
                        ->addDefaultsIfNotSet()
348 58
                        ->children()
349 58
                            ->arrayNode('query')
350 58
                                ->canBeDisabled()
351 58
                                ->children()
352 58
                                    ->scalarNode('parameter_name')->defaultValue('version')->end()
353 58
                                ->end()
354 58
                            ->end()
355 58
                            ->arrayNode('custom_header')
356 58
                                ->canBeDisabled()
357 58
                                ->children()
358 58
                                    ->scalarNode('header_name')->defaultValue('X-Accept-Version')->end()
359 58
                                ->end()
360 58
                            ->end()
361 58
                            ->arrayNode('media_type')
362 58
                                ->canBeDisabled()
363 58
                                ->children()
364 58
                                    ->scalarNode('regex')->defaultValue('/(v|version)=(?P<version>[0-9\.]+)/')->end()
365 58
                                ->end()
366 58
                            ->end()
367 58
                        ->end()
368 58
                    ->end()
369 58
                    ->arrayNode('guessing_order')
370 58
                        ->defaultValue(['query', 'custom_header', 'media_type'])
371 58
                        ->validate()
372
                            ->ifTrue(function ($v) {
373
                                foreach ($v as $resolver) {
374
                                    if (!in_array($resolver, ['query', 'custom_header', 'media_type'])) {
375
                                        return true;
376
                                    }
377
                                }
378 58
                            })
379 58
                            ->thenInvalid('Versioning guessing order can only contain "query", "custom_header", "media_type".')
380 58
                        ->end()
381 58
                        ->prototype('scalar')->end()
382 58
                    ->end()
383 58
                ->end()
384 58
            ->end()
385 58
        ->end();
386 58
    }
387
388 58
    private function addExceptionSection(ArrayNodeDefinition $rootNode): void
389
    {
390
        $rootNode
391 58
            ->children()
392 58
                ->arrayNode('exception')
393 58
                    ->fixXmlConfig('code', 'codes')
394 58
                    ->fixXmlConfig('message', 'messages')
395 58
                    ->addDefaultsIfNotSet()
396 58
                    ->canBeEnabled()
397 58
                    ->children()
398 58
                        ->booleanNode('map_exception_codes')
399 58
                            ->defaultFalse()
400 58
                            ->info('Enables an event listener that maps exception codes to response status codes based on the map configured with the "fos_rest.exception.codes" option.')
401 58
                        ->end()
402 58
                        ->booleanNode('exception_listener')
403 58
                            ->defaultValue(false)
404 58
                            ->validate()
405 58
                                ->ifTrue()
406 58
                                ->thenInvalid('only "false" is supported')
407 58
                            ->end()
408 58
                        ->end()
409 58
                        ->booleanNode('serialize_exceptions')
410 58
                            ->defaultValue(false)
411 58
                            ->validate()
412 58
                                ->ifTrue()
413 58
                                ->thenInvalid('only "false" is supported')
414 58
                            ->end()
415 58
                        ->end()
416 58
                        ->enumNode('flatten_exception_format')
417 58
                            ->defaultValue('legacy')
418 58
                            ->values(['legacy', 'rfc7807'])
419 58
                        ->end()
420 58
                        ->booleanNode('serializer_error_renderer')->defaultValue(false)->end()
421 58
                        ->arrayNode('codes')
422 58
                            ->useAttributeAsKey('name')
423 58
                            ->beforeNormalization()
424 58
                                ->ifArray()
425
                                ->then(function (array $items) {
426 13
                                    foreach ($items as &$item) {
427 13
                                        if (is_int($item)) {
428 3
                                            continue;
429
                                        }
430
431 10
                                        if (!defined(sprintf('%s::%s', Response::class, $item))) {
432 9
                                            throw new InvalidConfigurationException(sprintf('Invalid HTTP code in fos_rest.exception.codes, see %s for all valid codes.', Response::class));
433
                                        }
434
435 1
                                        $item = constant(sprintf('%s::%s', Response::class, $item));
436
                                    }
437
438 4
                                    return $items;
439 58
                                })
440 58
                            ->end()
441 58
                            ->prototype('integer')->end()
442
443 58
                            ->validate()
444 58
                            ->ifArray()
445
                                ->then(function (array $items) {
446 4
                                    foreach ($items as $class => $code) {
447 4
                                        $this->testExceptionExists($class);
448
                                    }
449
450 3
                                    return $items;
451 58
                                })
452 58
                            ->end()
453 58
                        ->end()
454 58
                        ->arrayNode('messages')
455 58
                            ->useAttributeAsKey('name')
456 58
                            ->prototype('boolean')->end()
457 58
                            ->validate()
458 58
                                ->ifArray()
459
                                ->then(function (array $items) {
460 9
                                    foreach ($items as $class => $nomatter) {
461 9
                                        $this->testExceptionExists($class);
462
                                    }
463
464 8
                                    return $items;
465 58
                                })
466 58
                            ->end()
467 58
                        ->end()
468 58
                        ->booleanNode('debug')
469 58
                            ->defaultValue($this->debug)
470 58
                        ->end()
471 58
                    ->end()
472 58
                ->end()
473 58
            ->end();
474 58
    }
475
476 13
    private function testExceptionExists(string $throwable): void
477
    {
478 13
        if (!is_subclass_of($throwable, \Throwable::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Throwable::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
479 2
            throw new InvalidConfigurationException(sprintf('FOSRestBundle exception mapper: Could not load class "%s" or the class does not extend from "%s". Most probably this is a configuration problem.', $throwable, \Throwable::class));
480
        }
481 11
    }
482
}
483