Completed
Pull Request — master (#2223)
by Christian
01:53
created

Configuration   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 429
Duplicated Lines 1.4 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 94.75%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 4
dl 6
loc 429
ccs 343
cts 362
cp 0.9475
rs 10
c 0
b 0
f 0

8 Methods

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