Completed
Push — master ( 7a0390...1e4134 )
by Christian
03:04
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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