Completed
Push — master ( b1054d...eba5f2 )
by Christian
07:28 queued 11s
created

Configuration::addVersioningSection()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 3.0028

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 41
cts 44
cp 0.9318
rs 9.1127
c 0
b 0
f 0
cc 3
nc 1
nop 1
crap 3.0028
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
    /**
35
     * Default debug mode value.
36
     *
37
     * @var bool
38
     */
39
    private $debug;
40
41
    /**
42
     * @param bool $debug
43
     */
44 65
    public function __construct($debug)
45
    {
46 65
        $this->debug = (bool) $debug;
47 65
    }
48
49
    /**
50
     * Generates the configuration tree.
51
     *
52
     * @return TreeBuilder
53
     */
54 64
    public function getConfigTreeBuilder()
55
    {
56 64
        $treeBuilder = new TreeBuilder('fos_rest');
57
58 64
        $rootNode = $treeBuilder->getRootNode();
59
60
        $rootNode
61 64
            ->children()
62 64
                ->scalarNode('disable_csrf_role')->defaultNull()->end()
63 64
                ->arrayNode('access_denied_listener')
64 64
                    ->canBeEnabled()
65 64
                    ->beforeNormalization()
66 View Code Duplication
                        ->ifArray()->then(function ($v) {
67
                            if (!empty($v) && empty($v['formats'])) {
68
                                unset($v['enabled']);
69
                                $v = ['enabled' => true, 'formats' => $v];
70
                            }
71
72
                            return $v;
73 64
                        })
74 64
                    ->end()
75 64
                    ->fixXmlConfig('format', 'formats')
76 64
                    ->children()
77 64
                        ->scalarNode('service')->defaultNull()->end()
78 64
                        ->arrayNode('formats')
79 64
                            ->useAttributeAsKey('name')
80 64
                            ->prototype('boolean')->end()
81 64
                        ->end()
82 64
                    ->end()
83 64
                ->end()
84 64
                ->scalarNode('unauthorized_challenge')->defaultNull()->end()
85 64
                ->arrayNode('param_fetcher_listener')
86 64
                    ->beforeNormalization()
87 64
                        ->ifString()
88 View Code Duplication
                        ->then(function ($v) {
89 1
                            return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
90 64
                        })
91 64
                    ->end()
92 64
                    ->canBeEnabled()
93 64
                    ->children()
94 64
                        ->booleanNode('force')->defaultFalse()->end()
95 64
                        ->scalarNode('service')->defaultNull()->end()
96 64
                    ->end()
97 64
                ->end()
98 64
                ->scalarNode('cache_dir')->cannotBeEmpty()->defaultValue('%kernel.cache_dir%/fos_rest')->end()
99 64
                ->arrayNode('allowed_methods_listener')
100 64
                    ->canBeEnabled()
101 64
                    ->children()
102 64
                        ->scalarNode('service')->defaultNull()->end()
103 64
                    ->end()
104 64
                ->end()
105 64
                ->arrayNode('routing_loader')
106 64
                    ->addDefaultsIfNotSet()
107 64
                    ->children()
108 64
                        ->booleanNode('parse_controller_name')
109 64
                            ->defaultValue(false)
110 64
                        ->end()
111 64
                        ->scalarNode('default_format')->defaultNull()->end()
112 64
                        ->scalarNode('prefix_methods')->defaultTrue()->end()
113 64
                        ->scalarNode('include_format')->defaultTrue()->end()
114 64
                    ->end()
115 64
                ->end()
116 64
                ->arrayNode('body_converter')
117 64
                    ->canBeEnabled()
118 64
                    ->children()
119 64
                        ->scalarNode('validate')
120 64
                            ->defaultFalse()
121 64
                            ->beforeNormalization()
122 64
                                ->ifTrue()
123
                                ->then(function ($value) {
124 1
                                    if (!class_exists(OptionsResolver::class)) {
125
                                        throw new InvalidConfigurationException("'body_converter.validate: true' requires OptionsResolver component installation ( composer require symfony/options-resolver )");
126
                                    }
127
128 1
                                    return $value;
129 64
                                })
130 64
                            ->end()
131 64
                        ->end()
132 64
                        ->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end()
133 64
                    ->end()
134 64
                ->end()
135 64
                ->arrayNode('service')
136 64
                    ->addDefaultsIfNotSet()
137 64
                    ->children()
138 64
                        ->scalarNode('router')->defaultValue('router')->end()
139 64
                        ->scalarNode('templating')
140 64
                            ->defaultNull()
141 64
                            ->validate()
142 64
                                ->ifString()
143 64
                                ->thenInvalid('only null is supported')
144 64
                            ->end()
145 64
                        ->end()
146 64
                        ->scalarNode('serializer')->defaultNull()->end()
147 64
                        ->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end()
148 64
                        ->scalarNode('inflector')->defaultValue('fos_rest.inflector.doctrine')->end()
149 64
                        ->scalarNode('validator')->defaultValue('validator')->end()
150 64
                    ->end()
151 64
                ->end()
152 64
                ->arrayNode('serializer')
153 64
                    ->addDefaultsIfNotSet()
154 64
                    ->children()
155 64
                        ->scalarNode('version')->defaultNull()->end()
156 64
                        ->arrayNode('groups')
157 64
                            ->prototype('scalar')->end()
158 64
                        ->end()
159 64
                        ->booleanNode('serialize_null')->defaultFalse()->end()
160 64
                    ->end()
161 64
                ->end()
162 64
                ->arrayNode('zone')
163 64
                    ->cannotBeOverwritten()
164 64
                    ->prototype('array')
165 64
                    ->fixXmlConfig('ip')
166 64
                    ->children()
167 64
                        ->scalarNode('path')
168 64
                            ->defaultNull()
169 64
                            ->info('use the urldecoded format')
170 64
                            ->example('^/path to resource/')
171 64
                        ->end()
172 64
                        ->scalarNode('host')->defaultNull()->end()
173 64
                        ->arrayNode('methods')
174
                            ->beforeNormalization()->ifString()->then(function ($v) {
175
                                return preg_split('/\s*,\s*/', $v);
176 64
                            })->end()
177 64
                            ->prototype('scalar')->end()
178 64
                        ->end()
179 64
                        ->arrayNode('ips')
180
                            ->beforeNormalization()->ifString()->then(function ($v) {
181 1
                                return array($v);
182 64
                            })->end()
183 64
                            ->prototype('scalar')->end()
184 64
                        ->end()
185 64
                    ->end()
186 64
                ->end()
187 64
            ->end()
188 64
        ->end();
189
190 64
        $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...
191 64
        $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...
192 64
        $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...
193 64
        $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...
194 64
        $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...
195
196 64
        return $treeBuilder;
197
    }
198
199 64
    private function addViewSection(ArrayNodeDefinition $rootNode)
200
    {
201
        $rootNode
202 64
            ->children()
203 64
                ->arrayNode('view')
204 64
                    ->fixXmlConfig('format', 'formats')
205 64
                    ->fixXmlConfig('mime_type', 'mime_types')
206 64
                    ->fixXmlConfig('force_redirect', 'force_redirects')
207 64
                    ->addDefaultsIfNotSet()
208 64
                    ->children()
209 64
                        ->scalarNode('default_engine')
210 64
                            ->defaultNull()
211 64
                            ->validate()
212 64
                                ->ifString()
213 64
                                ->thenInvalid('only null is supported')
214 64
                            ->end()
215 64
                        ->end()
216 64
                        ->arrayNode('force_redirects')
217 64
                            ->useAttributeAsKey('name')
218 64
                            ->defaultValue([])
219 64
                            ->validate()
220
                                ->ifTrue(function ($v) { return [] !== $v; })
221 64
                                ->thenInvalid('only the empty array is supported')
222 64
                            ->end()
223 64
                            ->prototype('boolean')->end()
224 64
                        ->end()
225 64
                        ->arrayNode('mime_types')
226 64
                            ->canBeEnabled()
227 64
                            ->beforeNormalization()
228 View Code Duplication
                                ->ifArray()->then(function ($v) {
229 1
                                    if (!empty($v) && empty($v['formats'])) {
230 1
                                        unset($v['enabled']);
231 1
                                        $v = ['enabled' => true, 'formats' => $v];
232
                                    }
233
234 1
                                    return $v;
235 64
                                })
236 64
                            ->end()
237 64
                            ->fixXmlConfig('format', 'formats')
238 64
                            ->children()
239 64
                                ->scalarNode('service')->defaultNull()->end()
240 64
                                ->arrayNode('formats')
241 64
                                    ->useAttributeAsKey('name')
242 64
                                    ->prototype('array')
243 64
                                        ->beforeNormalization()
244 64
                                            ->ifString()
245
                                            ->then(function ($v) { return array($v); })
246 64
                                        ->end()
247 64
                                        ->prototype('scalar')->end()
248 64
                                    ->end()
249 64
                                ->end()
250 64
                            ->end()
251 64
                        ->end()
252 64
                        ->arrayNode('formats')
253 64
                            ->useAttributeAsKey('name')
254 64
                            ->defaultValue(['json' => true, 'xml' => true])
255 64
                            ->prototype('boolean')->end()
256 64
                        ->end()
257 64
                        ->arrayNode('view_response_listener')
258 64
                            ->beforeNormalization()
259 64
                                ->ifString()
260 View Code Duplication
                                ->then(function ($v) {
261 4
                                    return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
262 64
                                })
263 64
                            ->end()
264 64
                            ->canBeEnabled()
265 64
                            ->children()
266 64
                                ->booleanNode('force')->defaultFalse()->end()
267 64
                                ->scalarNode('service')->defaultNull()->end()
268 64
                            ->end()
269 64
                        ->end()
270 64
                        ->scalarNode('failed_validation')->defaultValue(Response::HTTP_BAD_REQUEST)->end()
271 64
                        ->scalarNode('empty_content')->defaultValue(Response::HTTP_NO_CONTENT)->end()
272 64
                        ->booleanNode('serialize_null')->defaultFalse()->end()
273 64
                        ->arrayNode('jsonp_handler')
274 64
                            ->canBeUnset()
275 64
                            ->children()
276 64
                                ->scalarNode('callback_param')->defaultValue('callback')->end()
277 64
                                ->scalarNode('mime_type')->defaultValue('application/javascript+jsonp')->end()
278 64
                            ->end()
279 64
                        ->end()
280 64
                    ->end()
281 64
                ->end()
282 64
            ->end();
283 64
    }
284
285 64
    private function addBodyListenerSection(ArrayNodeDefinition $rootNode)
286
    {
287 64
        $decodersDefaultValue = ['json' => 'fos_rest.decoder.json'];
288 64
        if (class_exists(XmlEncoder::class)) {
289 64
            $decodersDefaultValue['xml'] = 'fos_rest.decoder.xml';
290
        }
291
        $rootNode
292 64
            ->children()
293 64
                ->arrayNode('body_listener')
294 64
                    ->fixXmlConfig('decoder', 'decoders')
295 64
                    ->addDefaultsIfNotSet()
296 64
                    ->canBeUnset()
297 64
                    ->canBeDisabled()
298 64
                    ->children()
299 64
                        ->scalarNode('service')->defaultNull()->end()
300 64
                        ->scalarNode('default_format')->defaultNull()->end()
301 64
                        ->booleanNode('throw_exception_on_unsupported_content_type')
302 64
                            ->defaultFalse()
303 64
                        ->end()
304 64
                        ->arrayNode('decoders')
305 64
                            ->useAttributeAsKey('name')
306 64
                            ->defaultValue($decodersDefaultValue)
307 64
                            ->prototype('scalar')->end()
308 64
                        ->end()
309 64
                        ->arrayNode('array_normalizer')
310 64
                            ->addDefaultsIfNotSet()
311 64
                            ->beforeNormalization()
312
                                ->ifString()->then(function ($v) {
313 1
                                    return ['service' => $v];
314 64
                                })
315 64
                            ->end()
316 64
                            ->children()
317 64
                                ->scalarNode('service')->defaultNull()->end()
318 64
                                ->booleanNode('forms')->defaultFalse()->end()
319 64
                            ->end()
320 64
                        ->end()
321 64
                    ->end()
322 64
                ->end()
323 64
            ->end();
324 64
    }
325
326 64
    private function addFormatListenerSection(ArrayNodeDefinition $rootNode)
327
    {
328
        $rootNode
329 64
            ->children()
330 64
                ->arrayNode('format_listener')
331 64
                    ->fixXmlConfig('rule', 'rules')
332 64
                    ->addDefaultsIfNotSet()
333 64
                    ->canBeUnset()
334 64
                    ->beforeNormalization()
335
                        ->ifTrue(function ($v) {
336
                            // check if we got an assoc array in rules
337 6
                            return isset($v['rules'])
338 6
                                && is_array($v['rules'])
339 6
                                && array_keys($v['rules']) !== range(0, count($v['rules']) - 1);
340 64
                        })
341
                        ->then(function ($v) {
342 1
                            $v['rules'] = [$v['rules']];
343
344 1
                            return $v;
345 64
                        })
346 64
                    ->end()
347 64
                    ->canBeEnabled()
348 64
                    ->children()
349 64
                        ->scalarNode('service')->defaultNull()->end()
350 64
                        ->arrayNode('rules')
351 64
                            ->performNoDeepMerging()
352 64
                            ->prototype('array')
353 64
                                ->fixXmlConfig('priority', 'priorities')
354 64
                                ->fixXmlConfig('attribute', 'attributes')
355 64
                                ->children()
356 64
                                    ->scalarNode('path')->defaultNull()->info('URL path info')->end()
357 64
                                    ->scalarNode('host')->defaultNull()->info('URL host name')->end()
358 64
                                    ->variableNode('methods')->defaultNull()->info('Method for URL')->end()
359 64
                                    ->arrayNode('attributes')
360 64
                                        ->useAttributeAsKey('name')
361 64
                                        ->prototype('variable')->end()
362 64
                                    ->end()
363 64
                                    ->booleanNode('stop')->defaultFalse()->end()
364 64
                                    ->booleanNode('prefer_extension')->defaultTrue()->end()
365 64
                                    ->scalarNode('fallback_format')->defaultValue('html')->end()
366 64
                                    ->arrayNode('priorities')
367
                                        ->beforeNormalization()->ifString()->then(function ($v) {
368
                                            return preg_split('/\s*,\s*/', $v);
369 64
                                        })->end()
370 64
                                        ->prototype('scalar')->end()
371 64
                                    ->end()
372 64
                                ->end()
373 64
                            ->end()
374 64
                        ->end()
375 64
                    ->end()
376 64
                ->end()
377 64
            ->end();
378 64
    }
379
380 64
    private function addVersioningSection(ArrayNodeDefinition $rootNode)
381
    {
382
        $rootNode
383 64
        ->children()
384 64
            ->arrayNode('versioning')
385 64
                ->canBeEnabled()
386 64
                ->children()
387 64
                    ->scalarNode('default_version')->defaultNull()->end()
388 64
                    ->arrayNode('resolvers')
389 64
                        ->addDefaultsIfNotSet()
390 64
                        ->children()
391 64
                            ->arrayNode('query')
392 64
                                ->canBeDisabled()
393 64
                                ->children()
394 64
                                    ->scalarNode('parameter_name')->defaultValue('version')->end()
395 64
                                ->end()
396 64
                            ->end()
397 64
                            ->arrayNode('custom_header')
398 64
                                ->canBeDisabled()
399 64
                                ->children()
400 64
                                    ->scalarNode('header_name')->defaultValue('X-Accept-Version')->end()
401 64
                                ->end()
402 64
                            ->end()
403 64
                            ->arrayNode('media_type')
404 64
                                ->canBeDisabled()
405 64
                                ->children()
406 64
                                    ->scalarNode('regex')->defaultValue('/(v|version)=(?P<version>[0-9\.]+)/')->end()
407 64
                                ->end()
408 64
                            ->end()
409 64
                        ->end()
410 64
                    ->end()
411 64
                    ->arrayNode('guessing_order')
412 64
                        ->defaultValue(['query', 'custom_header', 'media_type'])
413 64
                        ->validate()
414
                            ->ifTrue(function ($v) {
415
                                foreach ($v as $resolver) {
416
                                    if (!in_array($resolver, ['query', 'custom_header', 'media_type'])) {
417
                                        return true;
418
                                    }
419
                                }
420 64
                            })
421 64
                            ->thenInvalid('Versioning guessing order can only contain "query", "custom_header", "media_type".')
422 64
                        ->end()
423 64
                        ->prototype('scalar')->end()
424 64
                    ->end()
425 64
                ->end()
426 64
            ->end()
427 64
        ->end();
428 64
    }
429
430 64
    private function addExceptionSection(ArrayNodeDefinition $rootNode)
431
    {
432
        $rootNode
433 64
            ->children()
434 64
                ->arrayNode('exception')
435 64
                    ->fixXmlConfig('code', 'codes')
436 64
                    ->fixXmlConfig('message', 'messages')
437 64
                    ->addDefaultsIfNotSet()
438 64
                    ->canBeEnabled()
439 64
                    ->children()
440 64
                        ->scalarNode('exception_controller')->defaultValue('fos_rest.exception.controller::showAction')->end()
441 64
                        ->scalarNode('service')->defaultNull()->end()
442 64
                        ->arrayNode('codes')
443 64
                            ->useAttributeAsKey('name')
444 64
                            ->beforeNormalization()
445 64
                                ->ifArray()
446
                                ->then(function (array $items) {
447 13
                                    foreach ($items as &$item) {
448 13
                                        if (is_int($item)) {
449 3
                                            continue;
450
                                        }
451
452 10
                                        if (!defined('Symfony\Component\HttpFoundation\Response::'.$item)) {
453 9
                                            throw new InvalidConfigurationException('Invalid HTTP code in fos_rest.exception.codes, see Symfony\Component\HttpFoundation\Response for all valid codes.');
454
                                        }
455
456 1
                                        $item = constant('Symfony\Component\HttpFoundation\Response::'.$item);
457
                                    }
458
459 4
                                    return $items;
460 64
                                })
461 64
                            ->end()
462 64
                            ->prototype('integer')->end()
463
464 64
                            ->validate()
465 64
                            ->ifArray()
466
                                ->then(function (array $items) {
467 4
                                    foreach ($items as $class => $code) {
468 4
                                        $this->testExceptionExists($class);
469
                                    }
470
471 3
                                    return $items;
472 64
                                })
473 64
                            ->end()
474 64
                        ->end()
475 64
                        ->arrayNode('messages')
476 64
                            ->useAttributeAsKey('name')
477 64
                            ->prototype('boolean')->end()
478 64
                            ->validate()
479 64
                                ->ifArray()
480
                                ->then(function (array $items) {
481 4
                                    foreach ($items as $class => $nomatter) {
482 4
                                        $this->testExceptionExists($class);
483
                                    }
484
485 3
                                    return $items;
486 64
                                })
487 64
                            ->end()
488 64
                        ->end()
489 64
                        ->booleanNode('debug')
490 64
                            ->defaultValue($this->debug)
491 64
                        ->end()
492 64
                    ->end()
493 64
                ->end()
494 64
            ->end();
495 64
    }
496
497
    /**
498
     * Checks if an exception is loadable.
499
     *
500
     * @param string $exception Class to test
501
     *
502
     * @throws InvalidConfigurationException if the class was not found
503
     */
504 8
    private function testExceptionExists($exception)
505
    {
506 8
        if (!is_subclass_of($exception, \Exception::class) && !is_a($exception, \Exception::class, true)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Exception::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
507 2
            throw new InvalidConfigurationException("FOSRestBundle exception mapper: Could not load class '$exception' or the class does not extend from '\\Exception'. Most probably this is a configuration problem.");
508
        }
509 6
    }
510
}
511