Configuration::getConfigTreeBuilder()   F
last analyzed

Complexity

Conditions 21
Paths 2

Size

Total Lines 155

Duplication

Lines 34
Ratio 21.94 %

Code Coverage

Tests 106
CRAP Score 21.0091

Importance

Changes 0
Metric Value
dl 34
loc 155
ccs 106
cts 109
cp 0.9725
rs 3.3333
c 0
b 0
f 0
cc 21
nc 2
nop 0
crap 21.0091

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the FOSHttpCacheBundle 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\HttpCacheBundle\DependencyInjection;
13
14
use FOS\HttpCache\ProxyClient\Varnish;
15
use FOS\HttpCache\SymfonyCache\PurgeListener;
16
use FOS\HttpCache\SymfonyCache\PurgeTagsListener;
17
use FOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;
18
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
19
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
20
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
21
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
22
use Symfony\Component\Config\Definition\ConfigurationInterface;
23
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
24
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
25
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
26
27
/**
28
 * This class contains the configuration information for the bundle.
29
 *
30
 * This information is solely responsible for how the different configuration
31
 * sections are normalized, and merged.
32
 *
33
 * @author David de Boer <[email protected]>
34
 * @author David Buchmann <[email protected]>
35
 */
36
class Configuration implements ConfigurationInterface
37
{
38
    /**
39
     * @var bool
40
     */
41
    private $debug;
42
43
    /**
44
     * @param bool $debug Whether to use the debug mode
45
     */
46 60
    public function __construct($debug)
47
    {
48 60
        $this->debug = $debug;
49 60
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 60
    public function getConfigTreeBuilder()
55
    {
56 60
        $treeBuilder = new TreeBuilder('fos_http_cache');
57
58
        // Keep compatibility with symfony/config < 4.2
59 60
        if (!method_exists($treeBuilder, 'getRootNode')) {
60
            $rootNode = $treeBuilder->root('fos_http_cache');
0 ignored issues
show
Deprecated Code introduced by Karel Souffriau
The method Symfony\Component\Config...der\TreeBuilder::root() has been deprecated with message: since Symfony 4.3, pass the root name to the constructor instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
61
        } else {
62 60
            $rootNode = $treeBuilder->getRootNode();
63
        }
64
65
        $rootNode
66 60
            ->validate()
67 60
                ->ifTrue(function ($v) {
68 58
                    return $v['cache_manager']['enabled']
69 58
                        && !isset($v['proxy_client'])
70 58
                        && !isset($v['cache_manager']['custom_proxy_client'])
71
                    ;
72 60
                })
73 60 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
74 17
                    if ('auto' === $v['cache_manager']['enabled']) {
75 16
                        $v['cache_manager']['enabled'] = false;
76
77 16
                        return $v;
78
                    }
79
80 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client or specify a custom_proxy_client to use the cache_manager.');
81 60
                })
82 60
            ->end()
83 60
            ->validate()
84 60
                ->ifTrue(function ($v) {
85 57
                    return $v['tags']['enabled'] && !$v['cache_manager']['enabled'];
86 60
                })
87 60 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88 18
                    if ('auto' === $v['tags']['enabled']) {
89 17
                        $v['tags']['enabled'] = false;
90
91 17
                        return $v;
92
                    }
93
94 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for tag handling.');
95 60
                })
96 60
            ->end()
97 60
            ->validate()
98 60
                ->ifTrue(function ($v) {
99 56
                    return $v['invalidation']['enabled'] && !$v['cache_manager']['enabled'];
100 60
                })
101 60 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102 17
                    if ('auto' === $v['invalidation']['enabled']) {
103 16
                        $v['invalidation']['enabled'] = false;
104
105 16
                        return $v;
106
                    }
107
108 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for invalidation handling.');
109 60
                })
110 60
            ->end()
111 60
            ->validate()
112 60
                ->ifTrue(
113 60
                    function ($v) {
114 55
                        return false !== $v['user_context']['logout_handler']['enabled'];
115 60
                    }
116
                )
117 60
                ->then(function ($v) {
118 54
                    if (isset($v['cache_manager']['custom_proxy_client'])) {
119 5
                        $v['user_context']['logout_handler']['enabled'] = true;
120
121 5
                        return $v;
122
                    }
123
124 49
                    if (isset($v['proxy_client']['default'])
125 49
                        && in_array($v['proxy_client']['default'], ['varnish', 'symfony', 'noop'])
126
                    ) {
127
                        $v['user_context']['logout_handler']['enabled'] = true;
128
129
                        return $v;
130
                    }
131 49
                    if (isset($v['proxy_client']['varnish'])
132 26
                        || isset($v['proxy_client']['symfony'])
133 49
                        || isset($v['proxy_client']['noop'])
134
                    ) {
135 28
                        $v['user_context']['logout_handler']['enabled'] = true;
136
137 28
                        return $v;
138
                    }
139
140 21
                    if ('auto' === $v['user_context']['logout_handler']['enabled']) {
141 19
                        $v['user_context']['logout_handler']['enabled'] = false;
142
143 19
                        return $v;
144
                    }
145
146 2
                    throw new InvalidConfigurationException('To enable the user context logout handler, you need to configure a ban capable proxy_client.');
147 60
                })
148 60
            ->end()
149
            // Determine the default tags header for the varnish client, depending on whether we use BAN or xkey
150 60
            ->validate()
151 60
                ->ifTrue(
152 60 View Code Duplication
                    function ($v) {
0 ignored issues
show
Duplication introduced by Pete Lawrence
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
                        return
154 53
                            array_key_exists('proxy_client', $v)
155 53
                            && array_key_exists('varnish', $v['proxy_client'])
156 53
                            && empty($v['proxy_client']['varnish']['tags_header'])
157
                        ;
158 60
                    }
159
                )
160 60
                ->then(function ($v) {
161 20
                    $v['proxy_client']['varnish']['tags_header'] =
162 20
                        (Varnish::TAG_XKEY === $v['proxy_client']['varnish']['tag_mode'])
163 1
                        ? Varnish::DEFAULT_HTTP_HEADER_CACHE_XKEY
164 19
                        : Varnish::DEFAULT_HTTP_HEADER_CACHE_TAGS;
165
166 20
                    return $v;
167 60
                })
168 60
            ->end()
169
            // Determine the default tag response header, depending on whether we use BAN or xkey
170 60
            ->validate()
171 60
                ->ifTrue(
172 60
                    function ($v) {
173 53
                        return empty($v['tags']['response_header']);
174 60
                    }
175
                )
176 60
                ->then(function ($v) {
177 49
                    $v['tags']['response_header'] = $this->isVarnishXkey($v) ? 'xkey' : TagHeaderFormatter::DEFAULT_HEADER_NAME;
178
179 49
                    return $v;
180 60
                })
181 60
            ->end()
182
            // Determine the default separator for the tags header, depending on whether we use BAN or xkey
183 60
            ->validate()
184 60
                ->ifTrue(
185 60
                    function ($v) {
186 53
                        return empty($v['tags']['separator']);
187 60
                    }
188
                )
189 60
                ->then(function ($v) {
190 50
                    $v['tags']['separator'] = $this->isVarnishXkey($v) ? ' ' : ',';
191
192 50
                    return $v;
193 60
                })
194
        ;
195
196 60
        $this->addCacheableResponseSection($rootNode);
0 ignored issues
show
Compatibility introduced by David de Boer
$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...
197 60
        $this->addCacheControlSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
198 60
        $this->addProxyClientSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
199 60
        $this->addCacheManagerSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
200 60
        $this->addTagSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
201 60
        $this->addInvalidationSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
202 60
        $this->addUserContextListenerSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
203 60
        $this->addFlashMessageSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
204 60
        $this->addTestSection($rootNode);
0 ignored issues
show
Compatibility introduced by David de Boer
$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...
205 60
        $this->addDebugSection($rootNode);
0 ignored issues
show
Compatibility introduced by David Buchmann
$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...
206
207 60
        return $treeBuilder;
208
    }
209
210 50 View Code Duplication
    private function isVarnishXkey(array $v): bool
0 ignored issues
show
Duplication introduced by Pete Lawrence
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
    {
212 50
        return array_key_exists('proxy_client', $v)
213 50
            && array_key_exists('varnish', $v['proxy_client'])
214 50
            && Varnish::TAG_XKEY === $v['proxy_client']['varnish']['tag_mode']
215
        ;
216
    }
217
218 60
    private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
219
    {
220
        $rootNode
221 60
            ->children()
222 60
                ->arrayNode('cacheable')
223 60
                    ->addDefaultsIfNotSet()
224 60
                    ->children()
225 60
                        ->arrayNode('response')
226 60
                            ->addDefaultsIfNotSet()
227 60
                            ->children()
228 60
                                ->arrayNode('additional_status')
229 60
                                    ->prototype('scalar')->end()
230 60
                                    ->info('Additional response HTTP status codes that will be considered cacheable.')
231 60
                                ->end()
232 60
                                ->scalarNode('expression')
233 60
                                    ->defaultNull()
234 60
                                    ->info('Expression to decide whether response is cacheable. Replaces the default status codes.')
235 60
                            ->end()
236 60
                        ->end()
237
238 60
                        ->validate()
239 60
                            ->ifTrue(function ($v) {
240 6
                                return !empty($v['additional_status']) && !empty($v['expression']);
241 60
                            })
242 60
                            ->thenInvalid('You may not set both additional_status and expression.')
243 60
                        ->end()
244 60
                    ->end()
245 60
                ->end()
246 60
            ->end();
247 60
    }
248
249
    /**
250
     * Cache header control main section.
251
     *
252
     * @param ArrayNodeDefinition $rootNode
253
     */
254 60
    private function addCacheControlSection(ArrayNodeDefinition $rootNode)
255
    {
256
        $rules = $rootNode
257 60
            ->children()
258 60
                ->arrayNode('cache_control')
259 60
                    ->fixXmlConfig('rule')
260 60
                    ->children()
261 60
                        ->arrayNode('defaults')
262 60
                            ->addDefaultsIfNotSet()
263 60
                            ->children()
264 60
                                ->booleanNode('overwrite')
265 60
                                    ->info('Whether to overwrite existing cache headers')
266 60
                                    ->defaultFalse()
267 60
                                ->end()
268 60
                            ->end()
269 60
                        ->end()
270 60
                        ->arrayNode('rules')
271 60
                            ->prototype('array')
272 60
                                ->children();
273
274 60
        $this->addMatch($rules, true);
275
        $rules
276 60
            ->arrayNode('headers')
277 60
                ->isRequired()
278
                // todo validate there is some header defined
279 60
                ->children()
280 60
                    ->enumNode('overwrite')
281 60
                        ->info('Whether to overwrite cache headers for this rule, defaults to the cache_control.defaults.overwrite setting')
282 60
                        ->values(['default', true, false])
283 60
                        ->defaultValue('default')
284 60
                    ->end()
285 60
                    ->arrayNode('cache_control')
286 60
                        ->info('Add the specified cache control directives.')
287 60
                        ->children()
288 60
                            ->scalarNode('max_age')->end()
289 60
                            ->scalarNode('s_maxage')->end()
290 60
                            ->booleanNode('private')->end()
291 60
                            ->booleanNode('public')->end()
292 60
                            ->booleanNode('must_revalidate')->end()
293 60
                            ->booleanNode('proxy_revalidate')->end()
294 60
                            ->booleanNode('no_transform')->end()
295 60
                            ->booleanNode('no_cache')->end()
296 60
                            ->booleanNode('no_store')->end()
297 60
                            ->scalarNode('stale_if_error')->end()
298 60
                            ->scalarNode('stale_while_revalidate')->end()
299 60
                        ->end()
300 60
                    ->end()
301 60
                    ->enumNode('etag')
302 60
                        ->defaultValue(false)
303 60
                        ->treatTrueLike('strong')
304 60
                        ->info('Set a simple ETag which is just the md5 hash of the response body. '.
305 60
                               'You can specify which type of ETag you want by passing "strong" or "weak".')
306 60
                        ->values(['weak', 'strong', false])
307 60
                    ->end()
308 60
                    ->scalarNode('last_modified')
309 60
                        ->validate()
310 60
                            ->ifTrue(function ($v) {
311 2
                                if (is_string($v)) {
312 2
                                    new \DateTime($v);
313
                                }
314
315 1
                                return false;
316 60
                            })
317 60
                            ->thenInvalid('') // this will never happen as new DateTime will throw an exception if $v is no date
318 60
                        ->end()
319 60
                        ->info('Set a default last modified timestamp if none is set yet. Value must be parseable by DateTime')
320 60
                    ->end()
321 60
                    ->scalarNode('reverse_proxy_ttl')
322 60
                        ->defaultNull()
323 60
                        ->info('Specify an X-Reverse-Proxy-TTL header with a time in seconds for a caching proxy under your control.')
324 60
                    ->end()
325 60
                    ->arrayNode('vary')
326 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
327 2
                            return preg_split('/\s*,\s*/', $v);
328 60
                        })->end()
329 60
                        ->prototype('scalar')->end()
330 60
                        ->info('Define a list of additional headers on which the response varies.')
331 60
                    ->end()
332 60
                ->end()
333 60
            ->end()
334
        ;
335 60
    }
336
337
    /**
338
     * Shared configuration between cache control, tags and invalidation.
339
     *
340
     * @param NodeBuilder $rules
341
     * @param bool        $matchResponse whether to also add fields to match response
342
     */
343 60
    private function addMatch(NodeBuilder $rules, $matchResponse = false)
344
    {
345
        $match = $rules
346 60
            ->arrayNode('match')
347 60
                ->cannotBeOverwritten()
348 60
                ->isRequired()
349 60
                ->fixXmlConfig('method')
350 60
                ->fixXmlConfig('ip')
351 60
                ->fixXmlConfig('attribute')
352 60
                ->validate()
353 60
                    ->ifTrue(function ($v) {
354 14
                        return !empty($v['additional_response_status']) && !empty($v['match_response']);
355 60
                    })
356 60
                    ->thenInvalid('You may not set both additional_response_status and match_response.')
357 60
                ->end()
358 60
                ->children()
359 60
                    ->scalarNode('path')
360 60
                        ->defaultNull()
361 60
                        ->info('Request path.')
362 60
                    ->end()
363 60
                    ->scalarNode('query_string')
364 60
                        ->defaultNull()
365 60
                        ->info('Request query string.')
366 60
                    ->end()
367 60
                    ->scalarNode('host')
368 60
                        ->defaultNull()
369 60
                        ->info('Request host name.')
370 60
                    ->end()
371 60
                    ->arrayNode('methods')
372 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
373 3
                            return preg_split('/\s*,\s*/', $v);
374 60
                        })->end()
375 60
                        ->useAttributeAsKey('name')
376 60
                        ->prototype('scalar')->end()
377 60
                        ->info('Request HTTP methods.')
378 60
                    ->end()
379 60
                    ->arrayNode('ips')
380 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
381 3
                            return preg_split('/\s*,\s*/', $v);
382 60
                        })->end()
383 60
                        ->useAttributeAsKey('name')
384 60
                        ->prototype('scalar')->end()
385 60
                        ->info('List of client IPs.')
386 60
                    ->end()
387 60
                    ->arrayNode('attributes')
388 60
                        ->useAttributeAsKey('name')
389 60
                        ->prototype('scalar')->end()
390 60
                        ->info('Regular expressions on request attributes.')
391 60
                    ->end()
392
        ;
393 60
        if ($matchResponse) {
394
            $match
395 60
                ->arrayNode('additional_response_status')
396 60
                    ->prototype('scalar')->end()
397 60
                    ->info('Additional response HTTP status codes that will match. Replaces cacheable configuration.')
398 60
                ->end()
399 60
                ->scalarNode('match_response')
400 60
                    ->defaultNull()
401 60
                    ->info('Expression to decide whether response should be matched. Replaces cacheable configuration.')
402 60
                ->end()
403
            ;
404
        }
405 60
    }
406
407 60
    private function addProxyClientSection(ArrayNodeDefinition $rootNode)
408
    {
409
        $rootNode
410 60
            ->children()
411 60
                ->arrayNode('proxy_client')
412 60
                    ->children()
413 60
                        ->enumNode('default')
414 60
                            ->values(['varnish', 'nginx', 'symfony', 'noop'])
415 60
                            ->info('If you configure more than one proxy client, you need to specify which client is the default.')
416 60
                        ->end()
417 60
                        ->arrayNode('varnish')
418 60
                            ->fixXmlConfig('default_ban_header')
419 60
                            ->validate()
420 60
                                ->always(function ($v) {
421 24
                                    if (!count($v['default_ban_headers'])) {
422 23
                                        unset($v['default_ban_headers']);
423
                                    }
424
425 24
                                    return $v;
426 60
                                })
427 60
                            ->end()
428 60
                            ->children()
429 60
                                ->scalarNode('tags_header')
430 60
                                    ->info('HTTP header to use when sending tag invalidation requests to Varnish')
431 60
                                ->end()
432 60
                                ->scalarNode('header_length')
433 60
                                    ->info('Maximum header length when invalidating tags. If there are more tags to invalidate than fit into the header, the invalidation request is split into several requests.')
434 60
                                ->end()
435 60
                                ->arrayNode('default_ban_headers')
436 60
                                    ->useAttributeAsKey('name')
437 60
                                    ->info('Map of additional headers to include in each ban request.')
438 60
                                    ->prototype('scalar')->end()
439 60
                                ->end()
440 60
                                ->enumNode('tag_mode')
441 60
                                    ->info('If you can enable the xkey module in Varnish, use the purgekeys mode for more efficient tag handling')
442 60
                                    ->values(['ban', 'purgekeys'])
443 60
                                    ->defaultValue('ban')
444 60
                                ->end()
445 60
                                ->append($this->getHttpDispatcherNode())
446 60
                            ->end()
447 60
                        ->end()
448
449 60
                        ->arrayNode('nginx')
450 60
                            ->children()
451 60
                                ->scalarNode('purge_location')
452 60
                                    ->defaultValue(false)
453 60
                                    ->info('Path to trigger the purge on Nginx for different location purge.')
454 60
                                ->end()
455 60
                                ->append($this->getHttpDispatcherNode())
456 60
                            ->end()
457 60
                        ->end()
458
459 60
                        ->arrayNode('symfony')
460 60
                            ->children()
461 60
                                ->scalarNode('tags_header')
462 60
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_HEADER)
463 60
                                    ->info('HTTP header to use when sending tag invalidation requests to Symfony HttpCache')
464 60
                                ->end()
465 60
                                ->scalarNode('tags_method')
466 60
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_METHOD)
467 60
                                    ->info('HTTP method for sending tag invalidation requests to Symfony HttpCache')
468 60
                                ->end()
469 60
                                ->scalarNode('header_length')
470 60
                                    ->info('Maximum header length when invalidating tags. If there are more tags to invalidate than fit into the header, the invalidation request is split into several requests.')
471 60
                                ->end()
472 60
                                ->scalarNode('purge_method')
473 60
                                    ->defaultValue(PurgeListener::DEFAULT_PURGE_METHOD)
474 60
                                    ->info('HTTP method to use when sending purge requests to Symfony HttpCache')
475 60
                                ->end()
476 60
                                ->booleanNode('use_kernel_dispatcher')
477 60
                                    ->defaultFalse()
478 60
                                    ->info('Dispatches invalidation requests to the kernel directly instead of executing real HTTP requests. Requires special kernel setup! Refer to the documentation for more information.')
479 60
                                ->end()
480 60
                                ->append($this->getHttpDispatcherNode())
481 60
                            ->end()
482 60
                        ->end()
483
484 60
                        ->booleanNode('noop')->end()
485 60
                    ->end()
486 60
                    ->validate()
487 60
                        ->always()
488 60
                        ->then(function ($config) {
489 36
                            foreach ($config as $proxyName => $proxyConfig) {
490 36
                                $serversConfigured = isset($proxyConfig['http']) && isset($proxyConfig['http']['servers']) && \is_array($proxyConfig['http']['servers']);
491
492 36
                                if (!\in_array($proxyName, ['noop', 'default', 'symfony'])) {
493 29
                                    if (!$serversConfigured) {
494
                                        throw new \InvalidArgumentException(sprintf('The "http.servers" section must be defined for the proxy "%s"', $proxyName));
495
                                    }
496
497 29
                                    return $config;
498
                                }
499
500 7
                                if ('symfony' === $proxyName) {
501 4
                                    if (!$serversConfigured && false === $proxyConfig['use_kernel_dispatcher']) {
502 1
                                        throw new \InvalidArgumentException('Either configure the "http.servers" section or enable "proxy_client.symfony.use_kernel_dispatcher"');
503
                                    }
504
                                }
505
                            }
506
507 6
                            return $config;
508 60
                        })
509 60
                    ->end()
510 60
                ->end()
511 60
            ->end();
512 60
    }
513
514
    /**
515
     * Get the configuration node for a HTTP dispatcher in a proxy client.
516
     *
517
     * @return NodeDefinition
518
     */
519 60
    private function getHttpDispatcherNode()
520
    {
521 60
        $treeBuilder = new TreeBuilder('http');
522
523
        // Keep compatibility with symfony/config < 4.2
524 60
        if (!method_exists($treeBuilder, 'getRootNode')) {
525
            $node = $treeBuilder->root('http');
0 ignored issues
show
Deprecated Code introduced by Karel Souffriau
The method Symfony\Component\Config...der\TreeBuilder::root() has been deprecated with message: since Symfony 4.3, pass the root name to the constructor instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
526
        } else {
527 60
            $node = $treeBuilder->getRootNode();
528
        }
529
530
        $node
531 60
            ->fixXmlConfig('server')
532 60
            ->children()
533 60
                ->arrayNode('servers')
534 60
                    ->info('Addresses of the hosts the caching proxy is running on. May be hostname or ip, and with :port if not the default port 80.')
535 60
                    ->useAttributeAsKey('name')
536 60
                    ->isRequired()
537 60
                    ->requiresAtLeastOneElement()
538 60
                    ->prototype('scalar')->end()
539 60
                ->end()
540 60
                ->scalarNode('base_url')
541 60
                    ->defaultNull()
542 60
                    ->info('Default host name and optional path for path based invalidation.')
543 60
                ->end()
544 60
                ->scalarNode('http_client')
545 60
                    ->defaultNull()
546 60
                    ->info('Httplug async client service name to use for sending the requests.')
547 60
                ->end()
548 60
            ->end()
549
        ;
550
551 60
        return $node;
552
    }
553
554 60
    private function addTestSection(ArrayNodeDefinition $rootNode)
555
    {
556
        $rootNode
557 60
            ->children()
558 60
                ->arrayNode('test')
559 60
                    ->children()
560 60
                        ->scalarNode('cache_header')
561 60
                            ->defaultValue('X-Cache')
562 60
                            ->info('HTTP cache hit/miss header')
563 60
                        ->end()
564 60
                        ->arrayNode('proxy_server')
565 60
                            ->info('Configure how caching proxy will be run in your tests')
566 60
                            ->children()
567 60
                                ->enumNode('default')
568 60
                                    ->values(['varnish', 'nginx'])
569 60
                                    ->info('If you configure more than one proxy server, specify which client is the default.')
570 60
                                ->end()
571 60
                                ->arrayNode('varnish')
572 60
                                    ->children()
573 60
                                        ->scalarNode('config_file')->isRequired()->end()
574 60
                                        ->scalarNode('binary')->defaultValue('varnishd')->end()
575 60
                                        ->integerNode('port')->defaultValue(6181)->end()
576 60
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
577 60
                                    ->end()
578 60
                                ->end()
579 60
                                ->arrayNode('nginx')
580 60
                                    ->children()
581 60
                                        ->scalarNode('config_file')->isRequired()->end()
582 60
                                        ->scalarNode('binary')->defaultValue('nginx')->end()
583 60
                                        ->integerNode('port')->defaultValue(8080)->end()
584 60
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
585 60
                                    ->end()
586 60
                                ->end()
587 60
                            ->end()
588 60
                        ->end()
589 60
                    ->end()
590 60
                ->end()
591 60
            ->end();
592 60
    }
593
594
    /**
595
     * Cache manager main section.
596
     *
597
     * @param ArrayNodeDefinition $rootNode
598
     */
599 60
    private function addCacheManagerSection(ArrayNodeDefinition $rootNode)
600
    {
601
        $rootNode
602 60
            ->children()
603 60
                ->arrayNode('cache_manager')
604 60
                    ->addDefaultsIfNotSet()
605 60
                    ->beforeNormalization()
606 60
                        ->ifArray()
607 60
                        ->then(function ($v) {
608 10
                            $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
609
610 10
                            return $v;
611 60
                        })
612 60
                    ->end()
613 60
                    ->info('Configure the cache manager. Needs a proxy_client to be configured.')
614 60
                    ->children()
615 60
                        ->enumNode('enabled')
616 60
                            ->values([true, false, 'auto'])
617 60
                            ->defaultValue('auto')
618 60
                            ->info('Allows to disable the invalidation manager. Enabled by default if you configure a proxy client.')
619 60
                        ->end()
620 60
                        ->scalarNode('custom_proxy_client')
621 60
                            ->info('Service name of a custom proxy client to use. With a custom client, generate_url_type defaults to ABSOLUTE_URL and tag support needs to be explicitly enabled. If no custom proxy client is specified, the first proxy client you configured is used.')
622 60
                            ->cannotBeEmpty()
623 60
                        ->end()
624 60
                        ->enumNode('generate_url_type')
625 60
                            ->values([
626 60
                                'auto',
627
                                UrlGeneratorInterface::ABSOLUTE_PATH,
628
                                UrlGeneratorInterface::ABSOLUTE_URL,
629
                                UrlGeneratorInterface::NETWORK_PATH,
630
                                UrlGeneratorInterface::RELATIVE_PATH,
631
                            ])
632 60
                            ->defaultValue('auto')
633 60
                            ->info('Set what URLs to generate on invalidate/refresh Route. Auto means path if base_url is set on the default proxy client, full URL otherwise.')
634 60
                        ->end()
635 60
                    ->end()
636
        ;
637 60
    }
638
639 60
    private function addTagSection(ArrayNodeDefinition $rootNode)
640
    {
641
        $rules = $rootNode
642 60
            ->children()
643 60
                ->arrayNode('tags')
644 60
                    ->addDefaultsIfNotSet()
645 60
                    ->fixXmlConfig('rule')
646 60
                    ->children()
647 60
                        ->enumNode('enabled')
648 60
                            ->values([true, false, 'auto'])
649 60
                            ->defaultValue('auto')
650 60
                            ->info('Allows to disable tag support. Enabled by default if you configured the cache manager and have a proxy client that supports tagging.')
651 60
                        ->end()
652 60
                        ->arrayNode('annotations')
653 60
                            ->info('Annotations require the FrameworkExtraBundle. Because we can not detect whether annotations are used when the FrameworkExtraBundle is not available, this option must be set to false explicitly if the application does not use annotations.')
654 60
                            ->canBeDisabled()
655 60
                        ->end()
656 60
                        ->booleanNode('strict')->defaultFalse()->end()
657 60
                        ->scalarNode('expression_language')
658 60
                            ->defaultNull()
659 60
                            ->info('Service name of a custom ExpressionLanugage to use.')
660 60
                        ->end()
661 60
                        ->scalarNode('response_header')
662 60
                            ->defaultNull()
663 60
                            ->info('HTTP header that contains cache tags. Defaults to xkey-softpurge for Varnish xkey or X-Cache-Tags otherwise')
664 60
                        ->end()
665 60
                        ->scalarNode('separator')
666 60
                            ->defaultNull()
667 60
                            ->info('Character(s) to use to separate multiple tags. Defaults to " " for Varnish xkey or "," otherwise')
668 60
                        ->end()
669 60
                        ->scalarNode('max_header_value_length')
670 60
                            ->defaultNull()
671 60
                            ->info('If configured the tag header value will be split into multiple response headers of the same name (see "response_header" configuration key) that all do not exceed the configured "max_header_value_length" (recommended is 4KB = 4096) - configure in bytes.')
672 60
                        ->end()
673 60
                        ->arrayNode('rules')
674 60
                            ->prototype('array')
675 60
                                ->fixXmlConfig('tag')
676 60
                                ->fixXmlConfig('tag_expression')
677 60
                                ->validate()
678 60
                                    ->ifTrue(function ($v) {
679 4
                                        return !empty($v['tag_expressions']) && !class_exists(ExpressionLanguage::class);
680 60
                                    })
681 60
                                    ->thenInvalid('Configured a tag_expression but ExpressionLanugage is not available')
682 60
                                ->end()
683 60
                                ->children()
684
                        ;
685 60
        $this->addMatch($rules);
686
687
        $rules
688 60
            ->arrayNode('tags')
689 60
                ->prototype('scalar')
690 60
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
691 60
            ->end()->end()
692 60
            ->arrayNode('tag_expressions')
693 60
                ->prototype('scalar')
694 60
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
695 60
            ->end()
696
        ;
697 60
    }
698
699 60
    private function addInvalidationSection(ArrayNodeDefinition $rootNode)
700
    {
701
        $rules = $rootNode
702 60
            ->children()
703 60
                ->arrayNode('invalidation')
704 60
                    ->fixXmlConfig('rule')
705 60
                    ->addDefaultsIfNotSet()
706 60
                    ->children()
707 60
                        ->enumNode('enabled')
708 60
                            ->values([true, false, 'auto'])
709 60
                            ->defaultValue('auto')
710 60
                            ->info('Allows to disable the listener for invalidation. Enabled by default if the cache manager is configured. When disabled, the cache manager is no longer flushed automatically.')
711 60
                        ->end()
712 60
                        ->scalarNode('expression_language')
713 60
                            ->defaultNull()
714 60
                            ->info('Service name of a custom ExpressionLanugage to use.')
715 60
                        ->end()
716 60
                        ->arrayNode('rules')
717 60
                            ->info('Set what requests should invalidate which target routes.')
718 60
                            ->prototype('array')
719 60
                                ->fixXmlConfig('route')
720 60
                                ->children();
721
722 60
        $this->addMatch($rules);
723
        $rules
724 60
            ->arrayNode('routes')
725 60
                ->isRequired()
726 60
                ->requiresAtLeastOneElement()
727 60
                ->useAttributeAsKey('name')
728 60
                ->info('Target routes to invalidate when request is matched')
729 60
                ->prototype('array')
730 60
                    ->children()
731 60
                        ->booleanNode('ignore_extra_params')->defaultTrue()->end()
732 60
                    ->end()
733 60
                ->end()
734 60
            ->end();
735 60
    }
736
737
    /**
738
     * User context main section.
739
     *
740
     * @param ArrayNodeDefinition $rootNode
741
     */
742 60
    private function addUserContextListenerSection(ArrayNodeDefinition $rootNode)
743
    {
744
        $rootNode
745 60
            ->children()
746 60
                ->arrayNode('user_context')
747 60
                    ->info('Listener that returns the request for the user context hash as early as possible.')
748 60
                    ->addDefaultsIfNotSet()
749 60
                    ->canBeEnabled()
750 60
                    ->fixXmlConfig('user_identifier_header')
751 60
                    ->children()
752 60
                        ->arrayNode('match')
753 60
                            ->addDefaultsIfNotSet()
754 60
                            ->children()
755 60
                                ->scalarNode('matcher_service')
756 60
                                    ->defaultValue('fos_http_cache.user_context.request_matcher')
757 60
                                    ->info('Service id of a request matcher that tells whether the request is a context hash request.')
758 60
                                ->end()
759 60
                                ->scalarNode('accept')
760 60
                                    ->defaultValue('application/vnd.fos.user-context-hash')
761 60
                                    ->info('Specify the accept HTTP header used for context hash requests.')
762 60
                                ->end()
763 60
                                ->scalarNode('method')
764 60
                                    ->defaultNull()
765 60
                                    ->info('Specify the HTTP method used for context hash requests.')
766 60
                                ->end()
767 60
                            ->end()
768 60
                        ->end()
769 60
                        ->scalarNode('hash_cache_ttl')
770 60
                            ->defaultValue(0)
771 60
                            ->info('Cache the response for the hash for the specified number of seconds. Setting this to 0 will not cache those responses at all.')
772 60
                        ->end()
773 60
                        ->booleanNode('always_vary_on_context_hash')
774 60
                            ->defaultTrue()
775 60
                            ->info('Whether to always add the user context hash header name in the response Vary header.')
776 60
                        ->end()
777 60
                        ->arrayNode('user_identifier_headers')
778 60
                            ->prototype('scalar')->end()
779 60
                            ->defaultValue(['Cookie', 'Authorization'])
780 60
                            ->info('List of headers that contain the unique identifier for the user in the hash request.')
781 60
                        ->end()
782 60
                        ->scalarNode('session_name_prefix')
783 60
                            ->defaultValue(false)
784 60
                            ->info('Prefix for session cookies. Must match your PHP session configuration. Set to false to ignore the session in user context.')
785 60
                        ->end()
786 60
                        ->scalarNode('user_hash_header')
787 60
                            ->defaultValue('X-User-Context-Hash')
788 60
                            ->info('Name of the header that contains the hash information for the context.')
789 60
                        ->end()
790 60
                        ->booleanNode('role_provider')
791 60
                            ->defaultFalse()
792 60
                            ->info('Whether to enable a provider that automatically adds all roles of the current user to the context.')
793 60
                        ->end()
794 60
                        ->arrayNode('logout_handler')
795 60
                            ->addDefaultsIfNotSet()
796 60
                            ->canBeEnabled()
797 60
                            ->children()
798 60
                                ->enumNode('enabled')
799 60
                                    ->values([true, false, 'auto'])
800 60
                                    ->defaultValue('auto')
801 60
                                    ->info('Whether to enable the user context logout handler.')
802 60
                                ->end()
803 60
                            ->end()
804 60
                        ->end()
805 60
                    ->end()
806 60
                ->end()
807 60
            ->end()
808
        ;
809 60
    }
810
811 60
    private function addFlashMessageSection(ArrayNodeDefinition $rootNode)
812
    {
813
        $rootNode
814 60
            ->children()
815 60
                ->arrayNode('flash_message')
816 60
                    ->canBeUnset()
817 60
                    ->canBeEnabled()
818 60
                    ->info('Activate the flash message listener that puts flash messages into a cookie.')
819 60
                    ->children()
820 60
                        ->scalarNode('name')
821 60
                            ->defaultValue('flashes')
822 60
                            ->info('Name of the cookie to set for flashes.')
823 60
                        ->end()
824 60
                        ->scalarNode('path')
825 60
                            ->defaultValue('/')
826 60
                            ->info('Cookie path validity.')
827 60
                        ->end()
828 60
                        ->scalarNode('host')
829 60
                            ->defaultNull()
830 60
                            ->info('Cookie host name validity.')
831 60
                        ->end()
832 60
                        ->scalarNode('secure')
833 60
                            ->defaultFalse()
834 60
                            ->info('Whether the cookie should only be transmitted over a secure HTTPS connection from the client.')
835 60
                        ->end()
836 60
                    ->end()
837 60
                ->end()
838 60
            ->end();
839 60
    }
840
841 60
    private function addDebugSection(ArrayNodeDefinition $rootNode)
842
    {
843
        $rootNode
844 60
            ->children()
845 60
                ->arrayNode('debug')
846 60
                ->addDefaultsIfNotSet()
847 60
                ->canBeEnabled()
848 60
                ->children()
849 60
                    ->booleanNode('enabled')
850 60
                        ->defaultValue($this->debug)
851 60
                        ->info('Whether to send a debug header with the response to trigger a caching proxy to send debug information. If not set, defaults to kernel.debug.')
852 60
                    ->end()
853 60
                    ->scalarNode('header')
854 60
                        ->defaultValue('X-Cache-Debug')
855 60
                        ->info('The header to send if debug is true.')
856 60
                    ->end()
857 60
                ->end()
858 60
            ->end()
859 60
        ->end();
860 60
    }
861
}
862