Completed
Push — master ( db0ed5...1379ca )
by David
21s
created

src/DependencyInjection/Configuration.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 View Code Duplication
        if (method_exists(TreeBuilder::class, 'getRootNode')) {
0 ignored issues
show
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...
57
            $treeBuilder = new TreeBuilder('fos_http_cache');
0 ignored issues
show
The call to TreeBuilder::__construct() has too many arguments starting with 'fos_http_cache'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
58
            $rootNode = $treeBuilder->getRootNode();
0 ignored issues
show
The method getRootNode() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
59
        } else {
60 60
            $treeBuilder = new TreeBuilder();
61 60
            $rootNode = $treeBuilder->root('fos_http_cache');
62
        }
63
64
        $rootNode
65 60
            ->validate()
66 60
                ->ifTrue(function ($v) {
67 58
                    return $v['cache_manager']['enabled']
68 58
                        && !isset($v['proxy_client'])
69 58
                        && !isset($v['cache_manager']['custom_proxy_client'])
70
                    ;
71 60
                })
72 60 View Code Duplication
                ->then(function ($v) {
73 17
                    if ('auto' === $v['cache_manager']['enabled']) {
74 16
                        $v['cache_manager']['enabled'] = false;
75
76 16
                        return $v;
77
                    }
78
79 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client or specify a custom_proxy_client to use the cache_manager.');
80 60
                })
81 60
            ->end()
82 60
            ->validate()
83 60
                ->ifTrue(function ($v) {
84 57
                    return $v['tags']['enabled'] && !$v['cache_manager']['enabled'];
85 60
                })
86 60 View Code Duplication
                ->then(function ($v) {
87 18
                    if ('auto' === $v['tags']['enabled']) {
88 17
                        $v['tags']['enabled'] = false;
89
90 17
                        return $v;
91
                    }
92
93 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for tag handling.');
94 60
                })
95 60
            ->end()
96 60
            ->validate()
97 60
                ->ifTrue(function ($v) {
98 56
                    return $v['invalidation']['enabled'] && !$v['cache_manager']['enabled'];
99 60
                })
100 60 View Code Duplication
                ->then(function ($v) {
101 17
                    if ('auto' === $v['invalidation']['enabled']) {
102 16
                        $v['invalidation']['enabled'] = false;
103
104 16
                        return $v;
105
                    }
106
107 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for invalidation handling.');
108 60
                })
109 60
            ->end()
110 60
            ->validate()
111 60
                ->ifTrue(
112 60
                    function ($v) {
113 55
                        return false !== $v['user_context']['logout_handler']['enabled'];
114 60
                    }
115
                )
116 60
                ->then(function ($v) {
117 54
                    if (isset($v['cache_manager']['custom_proxy_client'])) {
118 5
                        $v['user_context']['logout_handler']['enabled'] = true;
119
120 5
                        return $v;
121
                    }
122
123 49
                    if (isset($v['proxy_client']['default'])
124 49
                        && in_array($v['proxy_client']['default'], ['varnish', 'symfony', 'noop'])
125
                    ) {
126
                        $v['user_context']['logout_handler']['enabled'] = true;
127
128
                        return $v;
129
                    }
130 49
                    if (isset($v['proxy_client']['varnish'])
131 26
                        || isset($v['proxy_client']['symfony'])
132 49
                        || isset($v['proxy_client']['noop'])
133
                    ) {
134 28
                        $v['user_context']['logout_handler']['enabled'] = true;
135
136 28
                        return $v;
137
                    }
138
139 21
                    if ('auto' === $v['user_context']['logout_handler']['enabled']) {
140 19
                        $v['user_context']['logout_handler']['enabled'] = false;
141
142 19
                        return $v;
143
                    }
144
145 2
                    throw new InvalidConfigurationException('To enable the user context logout handler, you need to configure a ban capable proxy_client.');
146 60
                })
147 60
            ->end()
148
            // Determine the default tags header for the varnish client, depending on whether we use BAN or xkey
149 60
            ->validate()
150 60
                ->ifTrue(
151 60 View Code Duplication
                    function ($v) {
152
                        return
153 53
                            array_key_exists('proxy_client', $v)
154 53
                            && array_key_exists('varnish', $v['proxy_client'])
155 53
                            && empty($v['proxy_client']['varnish']['tags_header'])
156
                        ;
157 60
                    }
158
                )
159 60
                ->then(function ($v) {
160 20
                    $v['proxy_client']['varnish']['tags_header'] =
161 20
                        (Varnish::TAG_XKEY === $v['proxy_client']['varnish']['tag_mode'])
162 1
                        ? Varnish::DEFAULT_HTTP_HEADER_CACHE_XKEY
163 19
                        : Varnish::DEFAULT_HTTP_HEADER_CACHE_TAGS;
164
165 20
                    return $v;
166 60
                })
167 60
            ->end()
168
            // Determine the default tag response header, depending on whether we use BAN or xkey
169 60
            ->validate()
170 60
                ->ifTrue(
171 60
                    function ($v) {
172 53
                        return empty($v['tags']['response_header']);
173 60
                    }
174
                )
175 60
                ->then(function ($v) {
176 49
                    $v['tags']['response_header'] = $this->isVarnishXkey($v) ? 'xkey' : TagHeaderFormatter::DEFAULT_HEADER_NAME;
177
178 49
                    return $v;
179 60
                })
180 60
            ->end()
181
            // Determine the default separator for the tags header, depending on whether we use BAN or xkey
182 60
            ->validate()
183 60
                ->ifTrue(
184 60
                    function ($v) {
185 53
                        return empty($v['tags']['separator']);
186 60
                    }
187
                )
188 60
                ->then(function ($v) {
189 50
                    $v['tags']['separator'] = $this->isVarnishXkey($v) ? ' ' : ',';
190
191 50
                    return $v;
192 60
                })
193
        ;
194
195 60
        $this->addCacheableResponseSection($rootNode);
196 60
        $this->addCacheControlSection($rootNode);
197 60
        $this->addProxyClientSection($rootNode);
198 60
        $this->addCacheManagerSection($rootNode);
199 60
        $this->addTagSection($rootNode);
200 60
        $this->addInvalidationSection($rootNode);
201 60
        $this->addUserContextListenerSection($rootNode);
202 60
        $this->addFlashMessageSection($rootNode);
203 60
        $this->addTestSection($rootNode);
204 60
        $this->addDebugSection($rootNode);
205
206 60
        return $treeBuilder;
207
    }
208
209 50 View Code Duplication
    private function isVarnishXkey(array $v): bool
210
    {
211 50
        return array_key_exists('proxy_client', $v)
212 50
            && array_key_exists('varnish', $v['proxy_client'])
213 50
            && Varnish::TAG_XKEY === $v['proxy_client']['varnish']['tag_mode']
214
        ;
215
    }
216
217 60
    private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
218
    {
219
        $rootNode
220 60
            ->children()
221 60
                ->arrayNode('cacheable')
222 60
                    ->addDefaultsIfNotSet()
223 60
                    ->children()
224 60
                        ->arrayNode('response')
225 60
                            ->addDefaultsIfNotSet()
226 60
                            ->children()
227 60
                                ->arrayNode('additional_status')
228 60
                                    ->prototype('scalar')->end()
229 60
                                    ->info('Additional response HTTP status codes that will be considered cacheable.')
230 60
                                ->end()
231 60
                                ->scalarNode('expression')
232 60
                                    ->defaultNull()
233 60
                                    ->info('Expression to decide whether response is cacheable. Replaces the default status codes.')
234 60
                            ->end()
235 60
                        ->end()
236
237 60
                        ->validate()
238 60
                            ->ifTrue(function ($v) {
239 6
                                return !empty($v['additional_status']) && !empty($v['expression']);
240 60
                            })
241 60
                            ->thenInvalid('You may not set both additional_status and expression.')
242 60
                        ->end()
243 60
                    ->end()
244 60
                ->end()
245 60
            ->end();
246 60
    }
247
248
    /**
249
     * Cache header control main section.
250
     *
251
     * @param ArrayNodeDefinition $rootNode
252
     */
253 60
    private function addCacheControlSection(ArrayNodeDefinition $rootNode)
254
    {
255
        $rules = $rootNode
256 60
            ->children()
257 60
                ->arrayNode('cache_control')
258 60
                    ->fixXmlConfig('rule')
259 60
                    ->children()
260 60
                        ->arrayNode('defaults')
261 60
                            ->addDefaultsIfNotSet()
262 60
                            ->children()
263 60
                                ->booleanNode('overwrite')
264 60
                                    ->info('Whether to overwrite existing cache headers')
265 60
                                    ->defaultFalse()
266 60
                                ->end()
267 60
                            ->end()
268 60
                        ->end()
269 60
                        ->arrayNode('rules')
270 60
                            ->prototype('array')
271 60
                                ->children();
272
273 60
        $this->addMatch($rules, true);
274
        $rules
275 60
            ->arrayNode('headers')
276 60
                ->isRequired()
277
                // todo validate there is some header defined
278 60
                ->children()
279 60
                    ->enumNode('overwrite')
280 60
                        ->info('Whether to overwrite cache headers for this rule, defaults to the cache_control.defaults.overwrite setting')
281 60
                        ->values(['default', true, false])
282 60
                        ->defaultValue('default')
283 60
                    ->end()
284 60
                    ->arrayNode('cache_control')
285 60
                        ->info('Add the specified cache control directives.')
286 60
                        ->children()
287 60
                            ->scalarNode('max_age')->end()
288 60
                            ->scalarNode('s_maxage')->end()
289 60
                            ->booleanNode('private')->end()
290 60
                            ->booleanNode('public')->end()
291 60
                            ->booleanNode('must_revalidate')->end()
292 60
                            ->booleanNode('proxy_revalidate')->end()
293 60
                            ->booleanNode('no_transform')->end()
294 60
                            ->booleanNode('no_cache')->end()
295 60
                            ->booleanNode('no_store')->end()
296 60
                            ->scalarNode('stale_if_error')->end()
297 60
                            ->scalarNode('stale_while_revalidate')->end()
298 60
                        ->end()
299 60
                    ->end()
300 60
                    ->enumNode('etag')
301 60
                        ->defaultValue(false)
302 60
                        ->treatTrueLike('strong')
303 60
                        ->info('Set a simple ETag which is just the md5 hash of the response body. '.
304 60
                               'You can specify which type of ETag you want by passing "strong" or "weak".')
305 60
                        ->values(['weak', 'strong', false])
306 60
                    ->end()
307 60
                    ->scalarNode('last_modified')
308 60
                        ->validate()
309 60
                            ->ifTrue(function ($v) {
310 2
                                if (is_string($v)) {
311 2
                                    new \DateTime($v);
312
                                }
313
314 1
                                return false;
315 60
                            })
316 60
                            ->thenInvalid('') // this will never happen as new DateTime will throw an exception if $v is no date
317 60
                        ->end()
318 60
                        ->info('Set a default last modified timestamp if none is set yet. Value must be parseable by DateTime')
319 60
                    ->end()
320 60
                    ->scalarNode('reverse_proxy_ttl')
321 60
                        ->defaultNull()
322 60
                        ->info('Specify an X-Reverse-Proxy-TTL header with a time in seconds for a caching proxy under your control.')
323 60
                    ->end()
324 60
                    ->arrayNode('vary')
325 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
326 2
                            return preg_split('/\s*,\s*/', $v);
327 60
                        })->end()
328 60
                        ->prototype('scalar')->end()
329 60
                        ->info('Define a list of additional headers on which the response varies.')
330 60
                    ->end()
331 60
                ->end()
332 60
            ->end()
333
        ;
334 60
    }
335
336
    /**
337
     * Shared configuration between cache control, tags and invalidation.
338
     *
339
     * @param NodeBuilder $rules
340
     * @param bool        $matchResponse whether to also add fields to match response
341
     */
342 60
    private function addMatch(NodeBuilder $rules, $matchResponse = false)
343
    {
344
        $match = $rules
345 60
            ->arrayNode('match')
346 60
                ->cannotBeOverwritten()
347 60
                ->isRequired()
348 60
                ->fixXmlConfig('method')
349 60
                ->fixXmlConfig('ip')
350 60
                ->fixXmlConfig('attribute')
351 60
                ->validate()
352 60
                    ->ifTrue(function ($v) {
353 14
                        return !empty($v['additional_response_status']) && !empty($v['match_response']);
354 60
                    })
355 60
                    ->thenInvalid('You may not set both additional_response_status and match_response.')
356 60
                ->end()
357 60
                ->children()
358 60
                    ->scalarNode('path')
359 60
                        ->defaultNull()
360 60
                        ->info('Request path.')
361 60
                    ->end()
362 60
                    ->scalarNode('query_string')
363 60
                        ->defaultNull()
364 60
                        ->info('Request query string.')
365 60
                    ->end()
366 60
                    ->scalarNode('host')
367 60
                        ->defaultNull()
368 60
                        ->info('Request host name.')
369 60
                    ->end()
370 60
                    ->arrayNode('methods')
371 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
372 3
                            return preg_split('/\s*,\s*/', $v);
373 60
                        })->end()
374 60
                        ->useAttributeAsKey('name')
375 60
                        ->prototype('scalar')->end()
376 60
                        ->info('Request HTTP methods.')
377 60
                    ->end()
378 60
                    ->arrayNode('ips')
379 60
                        ->beforeNormalization()->ifString()->then(function ($v) {
380 3
                            return preg_split('/\s*,\s*/', $v);
381 60
                        })->end()
382 60
                        ->useAttributeAsKey('name')
383 60
                        ->prototype('scalar')->end()
384 60
                        ->info('List of client IPs.')
385 60
                    ->end()
386 60
                    ->arrayNode('attributes')
387 60
                        ->useAttributeAsKey('name')
388 60
                        ->prototype('scalar')->end()
389 60
                        ->info('Regular expressions on request attributes.')
390 60
                    ->end()
391
        ;
392 60
        if ($matchResponse) {
393
            $match
394 60
                ->arrayNode('additional_response_status')
395 60
                    ->prototype('scalar')->end()
396 60
                    ->info('Additional response HTTP status codes that will match. Replaces cacheable configuration.')
397 60
                ->end()
398 60
                ->scalarNode('match_response')
399 60
                    ->defaultNull()
400 60
                    ->info('Expression to decide whether response should be matched. Replaces cacheable configuration.')
401 60
                ->end()
402
            ;
403
        }
404 60
    }
405
406 60
    private function addProxyClientSection(ArrayNodeDefinition $rootNode)
407
    {
408
        $rootNode
409 60
            ->children()
410 60
                ->arrayNode('proxy_client')
411 60
                    ->children()
412 60
                        ->enumNode('default')
413 60
                            ->values(['varnish', 'nginx', 'symfony', 'noop'])
414 60
                            ->info('If you configure more than one proxy client, you need to specify which client is the default.')
415 60
                        ->end()
416 60
                        ->arrayNode('varnish')
417 60
                            ->fixXmlConfig('default_ban_header')
418 60
                            ->validate()
419 60
                                ->always(function ($v) {
420 24
                                    if (!count($v['default_ban_headers'])) {
421 23
                                        unset($v['default_ban_headers']);
422
                                    }
423
424 24
                                    return $v;
425 60
                                })
426 60
                            ->end()
427 60
                            ->children()
428 60
                                ->scalarNode('tags_header')
429 60
                                    ->info('HTTP header to use when sending tag invalidation requests to Varnish')
430 60
                                ->end()
431 60
                                ->scalarNode('header_length')
432 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.')
433 60
                                ->end()
434 60
                                ->arrayNode('default_ban_headers')
435 60
                                    ->useAttributeAsKey('name')
436 60
                                    ->info('Map of additional headers to include in each ban request.')
437 60
                                    ->prototype('scalar')->end()
438 60
                                ->end()
439 60
                                ->enumNode('tag_mode')
440 60
                                    ->info('If you can enable the xkey module in Varnish, use the purgekeys mode for more efficient tag handling')
441 60
                                    ->values(['ban', 'purgekeys'])
442 60
                                    ->defaultValue('ban')
443 60
                                ->end()
444 60
                                ->append($this->getHttpDispatcherNode())
445 60
                            ->end()
446 60
                        ->end()
447
448 60
                        ->arrayNode('nginx')
449 60
                            ->children()
450 60
                                ->scalarNode('purge_location')
451 60
                                    ->defaultValue(false)
452 60
                                    ->info('Path to trigger the purge on Nginx for different location purge.')
453 60
                                ->end()
454 60
                                ->append($this->getHttpDispatcherNode())
455 60
                            ->end()
456 60
                        ->end()
457
458 60
                        ->arrayNode('symfony')
459 60
                            ->children()
460 60
                                ->scalarNode('tags_header')
461 60
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_HEADER)
462 60
                                    ->info('HTTP header to use when sending tag invalidation requests to Symfony HttpCache')
463 60
                                ->end()
464 60
                                ->scalarNode('tags_method')
465 60
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_METHOD)
466 60
                                    ->info('HTTP method for sending tag invalidation requests to Symfony HttpCache')
467 60
                                ->end()
468 60
                                ->scalarNode('header_length')
469 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.')
470 60
                                ->end()
471 60
                                ->scalarNode('purge_method')
472 60
                                    ->defaultValue(PurgeListener::DEFAULT_PURGE_METHOD)
473 60
                                    ->info('HTTP method to use when sending purge requests to Symfony HttpCache')
474 60
                                ->end()
475 60
                                ->booleanNode('use_kernel_dispatcher')
476 60
                                    ->defaultFalse()
477 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.')
478 60
                                ->end()
479 60
                                ->append($this->getHttpDispatcherNode())
480 60
                            ->end()
481 60
                        ->end()
482
483 60
                        ->booleanNode('noop')->end()
484 60
                    ->end()
485 60
                    ->validate()
486 60
                        ->always()
487 60
                        ->then(function ($config) {
488 36
                            foreach ($config as $proxyName => $proxyConfig) {
489 36
                                $serversConfigured = isset($proxyConfig['http']) && isset($proxyConfig['http']['servers']) && \is_array($proxyConfig['http']['servers']);
490
491 36
                                if (!\in_array($proxyName, ['noop', 'default', 'symfony'])) {
492 29
                                    if (!$serversConfigured) {
493
                                        throw new \InvalidArgumentException(sprintf('The "http.servers" section must be defined for the proxy "%s"', $proxyName));
494
                                    }
495
496 29
                                    return $config;
497
                                }
498
499 7
                                if ('symfony' === $proxyName) {
500 4
                                    if (!$serversConfigured && false === $proxyConfig['use_kernel_dispatcher']) {
501 7
                                        throw new \InvalidArgumentException('Either configure the "http.servers" section or enable "proxy_client.symfony.use_kernel_dispatcher"');
502
                                    }
503
                                }
504
                            }
505
506 6
                            return $config;
507 60
                        })
508 60
                    ->end()
509 60
                ->end()
510 60
            ->end();
511 60
    }
512
513
    /**
514
     * Get the configuration node for a HTTP dispatcher in a proxy client.
515
     *
516
     * @return NodeDefinition
517
     */
518 60
    private function getHttpDispatcherNode()
519
    {
520 60 View Code Duplication
        if (method_exists(TreeBuilder::class, 'getRootNode')) {
0 ignored issues
show
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...
521
            $treeBuilder = new TreeBuilder('http');
0 ignored issues
show
The call to TreeBuilder::__construct() has too many arguments starting with 'http'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
522
            $node = $treeBuilder->getRootNode();
0 ignored issues
show
The method getRootNode() does not seem to exist on object<Symfony\Component...on\Builder\TreeBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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