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