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

Configuration   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 451
Duplicated Lines 4.88 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 97.38%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 4
dl 22
loc 451
ccs 371
cts 381
cp 0.9738
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B getConfigTreeBuilder() 11 131 4
B addViewSection() 11 69 3
A addBodyListenerSection() 0 40 2
A addFormatListenerSection() 0 53 3
A addVersioningSection() 0 49 3
B addExceptionSection() 0 87 6
A testExceptionExists() 0 6 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 62
    public function __construct(bool $debug)
37
    {
38 62
        $this->debug = $debug;
39 62
    }
40
41 61
    public function getConfigTreeBuilder(): TreeBuilder
42
    {
43 61
        $treeBuilder = new TreeBuilder('fos_rest');
44
45 61
        $rootNode = $treeBuilder->getRootNode();
46
47
        $rootNode
48 61
            ->children()
49 61
                ->scalarNode('disable_csrf_role')->defaultNull()->end()
50 61
                ->arrayNode('access_denied_listener')
51 61
                    ->canBeEnabled()
52 61
                    ->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 61
                        })
61 61
                    ->end()
62 61
                    ->fixXmlConfig('format', 'formats')
63 61
                    ->children()
64 61
                        ->scalarNode('service')->defaultNull()->end()
65 61
                        ->arrayNode('formats')
66 61
                            ->useAttributeAsKey('name')
67 61
                            ->prototype('boolean')->end()
68 61
                        ->end()
69 61
                    ->end()
70 61
                ->end()
71 61
                ->scalarNode('unauthorized_challenge')->defaultNull()->end()
72 61
                ->arrayNode('param_fetcher_listener')
73 61
                    ->beforeNormalization()
74 61
                        ->ifString()
75 View Code Duplication
                        ->then(function ($v) {
76 1
                            return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
77 61
                        })
78 61
                    ->end()
79 61
                    ->canBeEnabled()
80 61
                    ->children()
81 61
                        ->booleanNode('force')->defaultFalse()->end()
82 61
                        ->scalarNode('service')->defaultNull()->end()
83 61
                    ->end()
84 61
                ->end()
85 61
                ->scalarNode('cache_dir')->cannotBeEmpty()->defaultValue('%kernel.cache_dir%/fos_rest')->end()
86 61
                ->arrayNode('allowed_methods_listener')
87 61
                    ->canBeEnabled()
88 61
                    ->children()
89 61
                        ->scalarNode('service')->defaultNull()->end()
90 61
                    ->end()
91 61
                ->end()
92 61
                ->booleanNode('routing_loader')
93 61
                    ->defaultValue(false)
94 61
                    ->validate()
95 61
                        ->ifTrue()
96 61
                        ->thenInvalid('only "false" is supported')
97 61
                    ->end()
98 61
                ->end()
99 61
                ->arrayNode('body_converter')
100 61
                    ->canBeEnabled()
101 61
                    ->children()
102 61
                        ->scalarNode('validate')
103 61
                            ->defaultFalse()
104 61
                            ->beforeNormalization()
105 61
                                ->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 61
                                })
113 61
                            ->end()
114 61
                        ->end()
115 61
                        ->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end()
116 61
                    ->end()
117 61
                ->end()
118 61
                ->arrayNode('service')
119 61
                    ->addDefaultsIfNotSet()
120 61
                    ->children()
121 61
                        ->scalarNode('serializer')->defaultNull()->end()
122 61
                        ->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end()
123 61
                        ->scalarNode('validator')->defaultValue('validator')->end()
124 61
                    ->end()
125 61
                ->end()
126 61
                ->arrayNode('serializer')
127 61
                    ->addDefaultsIfNotSet()
128 61
                    ->children()
129 61
                        ->scalarNode('version')->defaultNull()->end()
130 61
                        ->arrayNode('groups')
131 61
                            ->prototype('scalar')->end()
132 61
                        ->end()
133 61
                        ->booleanNode('serialize_null')->defaultFalse()->end()
134 61
                    ->end()
135 61
                ->end()
136 61
                ->arrayNode('zone')
137 61
                    ->cannotBeOverwritten()
138 61
                    ->prototype('array')
139 61
                    ->fixXmlConfig('ip')
140 61
                    ->children()
141 61
                        ->scalarNode('path')
142 61
                            ->defaultNull()
143 61
                            ->info('use the urldecoded format')
144 61
                            ->example('^/path to resource/')
145 61
                        ->end()
146 61
                        ->scalarNode('host')->defaultNull()->end()
147 61
                        ->arrayNode('methods')
148
                            ->beforeNormalization()->ifString()->then(function ($v) {
149
                                return preg_split('/\s*,\s*/', $v);
150 61
                            })->end()
151 61
                            ->prototype('scalar')->end()
152 61
                        ->end()
153 61
                        ->arrayNode('ips')
154
                            ->beforeNormalization()->ifString()->then(function ($v) {
155 1
                                return array($v);
156 61
                            })->end()
157 61
                            ->prototype('scalar')->end()
158 61
                        ->end()
159 61
                    ->end()
160 61
                ->end()
161 61
            ->end()
162 61
        ->end();
163
164 61
        $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 61
        $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 61
        $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 61
        $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 61
        $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 61
        return $treeBuilder;
171
    }
172
173 61
    private function addViewSection(ArrayNodeDefinition $rootNode): void
174
    {
175
        $rootNode
176 61
            ->children()
177 61
                ->arrayNode('view')
178 61
                    ->fixXmlConfig('format', 'formats')
179 61
                    ->fixXmlConfig('mime_type', 'mime_types')
180 61
                    ->fixXmlConfig('force_redirect', 'force_redirects')
181 61
                    ->addDefaultsIfNotSet()
182 61
                    ->children()
183 61
                        ->arrayNode('mime_types')
184 61
                            ->canBeEnabled()
185 61
                            ->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 61
                                })
194 61
                            ->end()
195 61
                            ->fixXmlConfig('format', 'formats')
196 61
                            ->children()
197 61
                                ->scalarNode('service')->defaultNull()->end()
198 61
                                ->arrayNode('formats')
199 61
                                    ->useAttributeAsKey('name')
200 61
                                    ->prototype('array')
201 61
                                        ->beforeNormalization()
202 61
                                            ->ifString()
203
                                            ->then(function ($v) { return array($v); })
204 61
                                        ->end()
205 61
                                        ->prototype('scalar')->end()
206 61
                                    ->end()
207 61
                                ->end()
208 61
                            ->end()
209 61
                        ->end()
210 61
                        ->arrayNode('formats')
211 61
                            ->useAttributeAsKey('name')
212 61
                            ->defaultValue(['json' => true, 'xml' => true])
213 61
                            ->prototype('boolean')->end()
214 61
                        ->end()
215 61
                        ->arrayNode('view_response_listener')
216 61
                            ->beforeNormalization()
217 61
                                ->ifString()
218 View Code Duplication
                                ->then(function ($v) {
219 4
                                    return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v];
220 61
                                })
221 61
                            ->end()
222 61
                            ->canBeEnabled()
223 61
                            ->children()
224 61
                                ->booleanNode('force')->defaultFalse()->end()
225 61
                                ->scalarNode('service')->defaultNull()->end()
226 61
                            ->end()
227 61
                        ->end()
228 61
                        ->scalarNode('failed_validation')->defaultValue(Response::HTTP_BAD_REQUEST)->end()
229 61
                        ->scalarNode('empty_content')->defaultValue(Response::HTTP_NO_CONTENT)->end()
230 61
                        ->booleanNode('serialize_null')->defaultFalse()->end()
231 61
                        ->arrayNode('jsonp_handler')
232 61
                            ->canBeUnset()
233 61
                            ->children()
234 61
                                ->scalarNode('callback_param')->defaultValue('callback')->end()
235 61
                                ->scalarNode('mime_type')->defaultValue('application/javascript+jsonp')->end()
236 61
                            ->end()
237 61
                        ->end()
238 61
                    ->end()
239 61
                ->end()
240 61
            ->end();
241 61
    }
242
243 61
    private function addBodyListenerSection(ArrayNodeDefinition $rootNode): void
244
    {
245 61
        $decodersDefaultValue = ['json' => 'fos_rest.decoder.json'];
246 61
        if (class_exists(XmlEncoder::class)) {
247 61
            $decodersDefaultValue['xml'] = 'fos_rest.decoder.xml';
248
        }
249
        $rootNode
250 61
            ->children()
251 61
                ->arrayNode('body_listener')
252 61
                    ->fixXmlConfig('decoder', 'decoders')
253 61
                    ->addDefaultsIfNotSet()
254 61
                    ->canBeUnset()
255 61
                    ->canBeEnabled()
256 61
                    ->children()
257 61
                        ->scalarNode('service')->defaultNull()->end()
258 61
                        ->scalarNode('default_format')->defaultNull()->end()
259 61
                        ->booleanNode('throw_exception_on_unsupported_content_type')
260 61
                            ->defaultFalse()
261 61
                        ->end()
262 61
                        ->arrayNode('decoders')
263 61
                            ->useAttributeAsKey('name')
264 61
                            ->defaultValue($decodersDefaultValue)
265 61
                            ->prototype('scalar')->end()
266 61
                        ->end()
267 61
                        ->arrayNode('array_normalizer')
268 61
                            ->addDefaultsIfNotSet()
269 61
                            ->beforeNormalization()
270
                                ->ifString()->then(function ($v) {
271 1
                                    return ['service' => $v];
272 61
                                })
273 61
                            ->end()
274 61
                            ->children()
275 61
                                ->scalarNode('service')->defaultNull()->end()
276 61
                                ->booleanNode('forms')->defaultFalse()->end()
277 61
                            ->end()
278 61
                        ->end()
279 61
                    ->end()
280 61
                ->end()
281 61
            ->end();
282 61
    }
283
284 61
    private function addFormatListenerSection(ArrayNodeDefinition $rootNode): void
285
    {
286
        $rootNode
287 61
            ->children()
288 61
                ->arrayNode('format_listener')
289 61
                    ->fixXmlConfig('rule', 'rules')
290 61
                    ->addDefaultsIfNotSet()
291 61
                    ->canBeUnset()
292 61
                    ->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 61
                        })
299
                        ->then(function ($v) {
300 1
                            $v['rules'] = [$v['rules']];
301
302 1
                            return $v;
303 61
                        })
304 61
                    ->end()
305 61
                    ->canBeEnabled()
306 61
                    ->children()
307 61
                        ->scalarNode('service')->defaultNull()->end()
308 61
                        ->arrayNode('rules')
309 61
                            ->performNoDeepMerging()
310 61
                            ->prototype('array')
311 61
                                ->fixXmlConfig('priority', 'priorities')
312 61
                                ->fixXmlConfig('attribute', 'attributes')
313 61
                                ->children()
314 61
                                    ->scalarNode('path')->defaultNull()->info('URL path info')->end()
315 61
                                    ->scalarNode('host')->defaultNull()->info('URL host name')->end()
316 61
                                    ->variableNode('methods')->defaultNull()->info('Method for URL')->end()
317 61
                                    ->arrayNode('attributes')
318 61
                                        ->useAttributeAsKey('name')
319 61
                                        ->prototype('variable')->end()
320 61
                                    ->end()
321 61
                                    ->booleanNode('stop')->defaultFalse()->end()
322 61
                                    ->booleanNode('prefer_extension')->defaultTrue()->end()
323 61
                                    ->scalarNode('fallback_format')->defaultValue('html')->end()
324 61
                                    ->arrayNode('priorities')
325
                                        ->beforeNormalization()->ifString()->then(function ($v) {
326
                                            return preg_split('/\s*,\s*/', $v);
327 61
                                        })->end()
328 61
                                        ->prototype('scalar')->end()
329 61
                                    ->end()
330 61
                                ->end()
331 61
                            ->end()
332 61
                        ->end()
333 61
                    ->end()
334 61
                ->end()
335 61
            ->end();
336 61
    }
337
338 61
    private function addVersioningSection(ArrayNodeDefinition $rootNode): void
339
    {
340
        $rootNode
341 61
        ->children()
342 61
            ->arrayNode('versioning')
343 61
                ->canBeEnabled()
344 61
                ->children()
345 61
                    ->scalarNode('default_version')->defaultNull()->end()
346 61
                    ->arrayNode('resolvers')
347 61
                        ->addDefaultsIfNotSet()
348 61
                        ->children()
349 61
                            ->arrayNode('query')
350 61
                                ->canBeDisabled()
351 61
                                ->children()
352 61
                                    ->scalarNode('parameter_name')->defaultValue('version')->end()
353 61
                                ->end()
354 61
                            ->end()
355 61
                            ->arrayNode('custom_header')
356 61
                                ->canBeDisabled()
357 61
                                ->children()
358 61
                                    ->scalarNode('header_name')->defaultValue('X-Accept-Version')->end()
359 61
                                ->end()
360 61
                            ->end()
361 61
                            ->arrayNode('media_type')
362 61
                                ->canBeDisabled()
363 61
                                ->children()
364 61
                                    ->scalarNode('regex')->defaultValue('/(v|version)=(?P<version>[0-9\.]+)/')->end()
365 61
                                ->end()
366 61
                            ->end()
367 61
                        ->end()
368 61
                    ->end()
369 61
                    ->arrayNode('guessing_order')
370 61
                        ->defaultValue(['query', 'custom_header', 'media_type'])
371 61
                        ->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 61
                            })
379 61
                            ->thenInvalid('Versioning guessing order can only contain "query", "custom_header", "media_type".')
380 61
                        ->end()
381 61
                        ->prototype('scalar')->end()
382 61
                    ->end()
383 61
                ->end()
384 61
            ->end()
385 61
        ->end();
386 61
    }
387
388 61
    private function addExceptionSection(ArrayNodeDefinition $rootNode): void
389
    {
390
        $rootNode
391 61
            ->children()
392 61
                ->arrayNode('exception')
393 61
                    ->fixXmlConfig('code', 'codes')
394 61
                    ->fixXmlConfig('message', 'messages')
395 61
                    ->addDefaultsIfNotSet()
396 61
                    ->canBeEnabled()
397 61
                    ->children()
398 61
                        ->booleanNode('map_exception_codes')
399 61
                            ->defaultFalse()
400 61
                            ->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 61
                        ->end()
402 61
                        ->booleanNode('exception_listener')
403 61
                            ->defaultValue(false)
404 61
                            ->validate()
405 61
                                ->ifTrue()
406 61
                                ->thenInvalid('only "false" is supported')
407 61
                            ->end()
408 61
                        ->end()
409 61
                        ->booleanNode('serialize_exceptions')
410 61
                            ->defaultValue(false)
411 61
                            ->validate()
412 61
                                ->ifTrue()
413 61
                                ->thenInvalid('only "false" is supported')
414 61
                            ->end()
415 61
                        ->end()
416 61
                        ->enumNode('flatten_exception_format')
417 61
                            ->defaultValue('legacy')
418 61
                            ->values(['legacy', 'rfc7807'])
419 61
                        ->end()
420 61
                        ->booleanNode('serializer_error_renderer')->defaultValue(false)->end()
421 61
                        ->arrayNode('codes')
422 61
                            ->useAttributeAsKey('name')
423 61
                            ->beforeNormalization()
424 61
                                ->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 61
                                })
440 61
                            ->end()
441 61
                            ->prototype('integer')->end()
442
443 61
                            ->validate()
444 61
                            ->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 61
                                })
452 61
                            ->end()
453 61
                        ->end()
454 61
                        ->arrayNode('messages')
455 61
                            ->useAttributeAsKey('name')
456 61
                            ->prototype('boolean')->end()
457 61
                            ->validate()
458 61
                                ->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 61
                                })
466 61
                            ->end()
467 61
                        ->end()
468 61
                        ->booleanNode('debug')
469 61
                            ->defaultValue($this->debug)
470 61
                        ->end()
471 61
                    ->end()
472 61
                ->end()
473 61
            ->end();
474 61
    }
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