Completed
Pull Request — master (#430)
by David
09:22 queued 07:36
created

Configuration::addTestSection()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 37
cts 37
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 37
nc 1
nop 1
crap 1
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\Symfony;
15
use FOS\HttpCache\ProxyClient\Varnish;
16
use FOS\HttpCache\SymfonyCache\PurgeListener;
17
use FOS\HttpCache\SymfonyCache\PurgeTagsListener;
18
use FOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;
19
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
20
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
21
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
22
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
23
use Symfony\Component\Config\Definition\ConfigurationInterface;
24
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
25
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
28
/**
29
 * This class contains the configuration information for the bundle.
30
 *
31
 * This information is solely responsible for how the different configuration
32
 * sections are normalized, and merged.
33
 *
34
 * @author David de Boer <[email protected]>
35
 * @author David Buchmann <[email protected]>
36
 */
37
class Configuration implements ConfigurationInterface
38
{
39
    /**
40
     * @var bool
41
     */
42
    private $debug;
43
44
    /**
45
     * @param bool $debug Whether to use the debug mode
46
     */
47 42
    public function __construct($debug)
48
    {
49 42
        $this->debug = $debug;
50 42
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 42
    public function getConfigTreeBuilder()
56
    {
57 42
        $treeBuilder = new TreeBuilder();
58 42
        $rootNode = $treeBuilder->root('fos_http_cache');
59
60
        $rootNode
61 42
            ->validate()
62 42
                ->ifTrue(function ($v) {
63 41
                    return $v['cache_manager']['enabled']
64 41
                        && !isset($v['proxy_client'])
65 41
                        && !isset($v['cache_manager']['custom_proxy_client'])
66
                    ;
67 42
                })
68 42 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by
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...
69 14
                    if ('auto' === $v['cache_manager']['enabled']) {
70 13
                        $v['cache_manager']['enabled'] = false;
71
72 13
                        return $v;
73
                    }
74
75 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client or specify a custom_proxy_client to use the cache_manager.');
76 42
                })
77 42
            ->end()
78 42
            ->validate()
79 42
                ->ifTrue(function ($v) {
80 40
                    return $v['tags']['enabled'] && !$v['cache_manager']['enabled'];
81 42
                })
82 42 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by
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...
83 15
                    if ('auto' === $v['tags']['enabled']) {
84 14
                        $v['tags']['enabled'] = false;
85
86 14
                        return $v;
87
                    }
88
89 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for tag handling.');
90 42
                })
91 42
            ->end()
92 42
            ->validate()
93 42
                ->ifTrue(function ($v) {
94 39
                    return $v['invalidation']['enabled'] && !$v['cache_manager']['enabled'];
95 42
                })
96 42 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by
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...
97 14
                    if ('auto' === $v['invalidation']['enabled']) {
98 13
                        $v['invalidation']['enabled'] = false;
99
100 13
                        return $v;
101
                    }
102
103 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for invalidation handling.');
104 42
                })
105 42
            ->end()
106 42
            ->validate()
107 42
                ->ifTrue(
108 42
                    function ($v) {
109 38
                        return $v['user_context']['logout_handler']['enabled']
110 38
                            && !isset($v['proxy_client']);
111 42
                    }
112
                )
113 42 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by
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...
114 15
                    if ('auto' === $v['user_context']['logout_handler']['enabled']) {
115 15
                        $v['user_context']['logout_handler']['enabled'] = false;
116
117 15
                        return $v;
118
                    }
119
120
                    throw new InvalidConfigurationException('You need to configure a proxy_client for the logout_handler.');
121 42
                })
122
        ;
123
124 42
        $this->addCacheableResponseSection($rootNode);
125 42
        $this->addCacheControlSection($rootNode);
126 42
        $this->addProxyClientSection($rootNode);
127 42
        $this->addCacheManagerSection($rootNode);
128 42
        $this->addTagSection($rootNode);
129 42
        $this->addInvalidationSection($rootNode);
130 42
        $this->addUserContextListenerSection($rootNode);
131 42
        $this->addFlashMessageSection($rootNode);
132 42
        $this->addTestSection($rootNode);
133 42
        $this->addDebugSection($rootNode);
134
135 42
        return $treeBuilder;
136
    }
137
138 42
    private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
139
    {
140
        $rootNode
141 42
            ->children()
142 42
                ->arrayNode('cacheable')
143 42
                    ->addDefaultsIfNotSet()
144 42
                    ->children()
145 42
                        ->arrayNode('response')
146 42
                            ->addDefaultsIfNotSet()
147 42
                            ->children()
148 42
                                ->arrayNode('additional_status')
149 42
                                    ->prototype('scalar')->end()
150 42
                                    ->info('Additional response HTTP status codes that will be considered cacheable.')
151 42
                                ->end()
152 42
                                ->scalarNode('expression')
153 42
                                    ->defaultNull()
154 42
                                    ->info('Expression to decide whether response is cacheable. Replaces the default status codes.')
155 42
                            ->end()
156 42
                        ->end()
157
158 42
                        ->validate()
159 42
                            ->ifTrue(function ($v) {
160 4
                                return !empty($v['additional_status']) && !empty($v['expression']);
161 42
                            })
162 42
                            ->thenInvalid('You may not set both additional_status and expression.')
163 42
                        ->end()
164 42
                    ->end()
165 42
                ->end()
166 42
            ->end();
167 42
    }
168
169
    /**
170
     * Cache header control main section.
171
     *
172
     * @param ArrayNodeDefinition $rootNode
173
     */
174 42
    private function addCacheControlSection(ArrayNodeDefinition $rootNode)
175
    {
176
        $rules = $rootNode
177 42
            ->children()
178 42
                ->arrayNode('cache_control')
179 42
                    ->fixXmlConfig('rule')
180 42
                    ->children()
181 42
                        ->arrayNode('defaults')
182 42
                            ->addDefaultsIfNotSet()
183 42
                            ->children()
184 42
                                ->booleanNode('overwrite')
185 42
                                    ->info('Whether to overwrite existing cache headers')
186 42
                                    ->defaultFalse()
187 42
                                ->end()
188 42
                            ->end()
189 42
                        ->end()
190 42
                        ->arrayNode('rules')
191 42
                            ->prototype('array')
192 42
                                ->children();
193
194 42
        $this->addMatch($rules, true);
195
        $rules
196 42
            ->arrayNode('headers')
197 42
                ->isRequired()
198
                // todo validate there is some header defined
199 42
                ->children()
200 42
                    ->enumNode('overwrite')
201 42
                        ->info('Whether to overwrite cache headers for this rule, defaults to the cache_control.defaults.overwrite setting')
202 42
                        ->values(['default', true, false])
203 42
                        ->defaultValue('default')
204 42
                    ->end()
205 42
                    ->arrayNode('cache_control')
206 42
                        ->info('Add the specified cache control directives.')
207 42
                        ->children()
208 42
                            ->scalarNode('max_age')->end()
209 42
                            ->scalarNode('s_maxage')->end()
210 42
                            ->booleanNode('private')->end()
211 42
                            ->booleanNode('public')->end()
212 42
                            ->booleanNode('must_revalidate')->end()
213 42
                            ->booleanNode('proxy_revalidate')->end()
214 42
                            ->booleanNode('no_transform')->end()
215 42
                            ->booleanNode('no_cache')->end()
216 42
                            ->booleanNode('no_store')->end()
217 42
                            ->scalarNode('stale_if_error')->end()
218 42
                            ->scalarNode('stale_while_revalidate')->end()
219 42
                        ->end()
220 42
                    ->end()
221 42
                    ->enumNode('etag')
222 42
                        ->defaultValue(false)
223 42
                        ->treatTrueLike('strong')
224 42
                        ->info('Set a simple ETag which is just the md5 hash of the response body. '.
225 42
                               'You can specify which type of ETag you want by passing "strong" or "weak".')
226 42
                        ->values(['weak', 'strong', false])
227 42
                    ->end()
228 42
                    ->scalarNode('last_modified')
229 42
                        ->validate()
230 42
                            ->ifTrue(function ($v) {
231 2
                                if (is_string($v)) {
232 2
                                    new \DateTime($v);
233
                                }
234
235 1
                                return false;
236 42
                            })
237 42
                            ->thenInvalid('') // this will never happen as new DateTime will throw an exception if $v is no date
238 42
                        ->end()
239 42
                        ->info('Set a default last modified timestamp if none is set yet. Value must be parseable by DateTime')
240 42
                    ->end()
241 42
                    ->scalarNode('reverse_proxy_ttl')
242 42
                        ->defaultNull()
243 42
                        ->info('Specify an X-Reverse-Proxy-TTL header with a time in seconds for a caching proxy under your control.')
244 42
                    ->end()
245 42
                    ->arrayNode('vary')
246 42
                        ->beforeNormalization()->ifString()->then(function ($v) {
247 2
                            return preg_split('/\s*,\s*/', $v);
248 42
                        })->end()
249 42
                        ->prototype('scalar')->end()
250 42
                        ->info('Define a list of additional headers on which the response varies.')
251 42
                    ->end()
252 42
                ->end()
253 42
            ->end()
254
        ;
255 42
    }
256
257
    /**
258
     * Shared configuration between cache control, tags and invalidation.
259
     *
260
     * @param NodeBuilder $rules
261
     * @param bool        $matchResponse whether to also add fields to match response
262
     */
263 42
    private function addMatch(NodeBuilder $rules, $matchResponse = false)
264
    {
265
        $match = $rules
266 42
            ->arrayNode('match')
267 42
                ->cannotBeOverwritten()
268 42
                ->isRequired()
269 42
                ->fixXmlConfig('method')
270 42
                ->fixXmlConfig('ip')
271 42
                ->fixXmlConfig('attribute')
272 42
                ->validate()
273 42
                    ->ifTrue(function ($v) {
274 14
                        return !empty($v['additional_response_status']) && !empty($v['match_response']);
275 42
                    })
276 42
                    ->thenInvalid('You may not set both additional_response_status and match_response.')
277 42
                ->end()
278 42
                ->children()
279 42
                    ->scalarNode('path')
280 42
                        ->defaultNull()
281 42
                        ->info('Request path.')
282 42
                    ->end()
283 42
                    ->scalarNode('query_string')
284 42
                        ->defaultNull()
285 42
                        ->info('Request query string.')
286 42
                    ->end()
287 42
                    ->scalarNode('host')
288 42
                        ->defaultNull()
289 42
                        ->info('Request host name.')
290 42
                    ->end()
291 42
                    ->arrayNode('methods')
292 42
                        ->beforeNormalization()->ifString()->then(function ($v) {
293 3
                            return preg_split('/\s*,\s*/', $v);
294 42
                        })->end()
295 42
                        ->useAttributeAsKey('name')
296 42
                        ->prototype('scalar')->end()
297 42
                        ->info('Request HTTP methods.')
298 42
                    ->end()
299 42
                    ->arrayNode('ips')
300 42
                        ->beforeNormalization()->ifString()->then(function ($v) {
301 3
                            return preg_split('/\s*,\s*/', $v);
302 42
                        })->end()
303 42
                        ->useAttributeAsKey('name')
304 42
                        ->prototype('scalar')->end()
305 42
                        ->info('List of client IPs.')
306 42
                    ->end()
307 42
                    ->arrayNode('attributes')
308 42
                        ->useAttributeAsKey('name')
309 42
                        ->prototype('scalar')->end()
310 42
                        ->info('Regular expressions on request attributes.')
311 42
                    ->end()
312
        ;
313 42
        if ($matchResponse) {
314
            $match
315 42
                ->arrayNode('additional_response_status')
316 42
                    ->prototype('scalar')->end()
317 42
                    ->info('Additional response HTTP status codes that will match. Replaces cacheable configuration.')
318 42
                ->end()
319 42
                ->scalarNode('match_response')
320 42
                    ->defaultNull()
321 42
                    ->info('Expression to decide whether response should be matched. Replaces cacheable configuration.')
322 42
                ->end()
323
            ;
324
        }
325 42
    }
326
327 42
    private function addProxyClientSection(ArrayNodeDefinition $rootNode)
328
    {
329
        $rootNode
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method children() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
330 42
            ->children()
331 42
                ->arrayNode('proxy_client')
332 42
                    ->children()
333 42
                        ->enumNode('default')
334 42
                            ->values(['varnish', 'nginx', 'symfony', 'noop'])
335 42
                            ->info('If you configure more than one proxy client, you need to specify which client is the default.')
336 42
                        ->end()
337 42
                        ->arrayNode('varnish')
338 42
                            ->fixXmlConfig('default_ban_header')
339 42
                            ->validate()
340 42
                                ->always(function ($v) {
341 16
                                    if (!count($v['default_ban_headers'])) {
342 15
                                        unset($v['default_ban_headers']);
343
                                    }
344
345 16
                                    return $v;
346 42
                                })
347 42
                            ->end()
348 42
                            ->children()
349 42
                                ->scalarNode('tags_header')
350 42
                                    ->defaultValue(Varnish::DEFAULT_HTTP_HEADER_CACHE_TAGS)
351 42
                                    ->info('HTTP header to use when sending tag invalidation requests to Varnish')
352 42
                                ->end()
353 42
                                ->scalarNode('header_length')
354 42
                                    ->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.')
355 42
                                ->end()
356 42
                                ->arrayNode('default_ban_headers')
357 42
                                    ->useAttributeAsKey('name')
358 42
                                    ->info('Map of additional headers to include in each ban request.')
359 42
                                    ->prototype('scalar')->end()
360 42
                                ->end()
361 42
                                ->append($this->getHttpDispatcherNode())
362 42
                            ->end()
363 42
                        ->end()
364
365 42
                        ->arrayNode('nginx')
366 42
                            ->children()
367 42
                                ->scalarNode('purge_location')
368 42
                                    ->defaultValue(false)
369 42
                                    ->info('Path to trigger the purge on Nginx for different location purge.')
370 42
                                ->end()
371 42
                                ->append($this->getHttpDispatcherNode())
372 42
                            ->end()
373 42
                        ->end()
374
375 42
                        ->arrayNode('symfony')
376 42
                            ->children()
377 42
                                ->scalarNode('tags_header')
378 42
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_HEADER)
379 42
                                    ->info('HTTP header to use when sending tag invalidation requests to Symfony HttpCache')
380 42
                                ->end()
381 42
                                ->scalarNode('tags_method')
382 42
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_METHOD)
383 42
                                    ->info('HTTP method for sending tag invalidation requests to Symfony HttpCache')
384 42
                                ->end()
385 42
                                ->scalarNode('header_length')
386 42
                                    ->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.')
387 42
                                ->end()
388 42
                                ->scalarNode('purge_method')
389 42
                                    ->defaultValue(PurgeListener::DEFAULT_PURGE_METHOD)
390 42
                                    ->info('HTTP method to use when sending purge requests to Symfony HttpCache')
391 42
                                ->end()
392 42
                                ->append($this->getHttpDispatcherNode())
393 42
                            ->end()
394 42
                        ->end()
395
396 42
                        ->booleanNode('noop')->end()
397
398 42
                    ->end()
399 42
                ->end()
400 42
            ->end();
401 42
    }
402
403
    /**
404
     * Get the configuration node for a HTTP dispatcher in a proxy client.
405
     *
406
     * @return NodeDefinition
407
     */
408 42
    private function getHttpDispatcherNode()
409
    {
410 42
        $treeBuilder = new TreeBuilder();
411 42
        $node = $treeBuilder->root('http');
412
413
        $node
414 42
            ->fixXmlConfig('server')
415 42
            ->isRequired()
416 42
            ->children()
417 42
                ->arrayNode('servers')
418 42
                    ->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.')
419 42
                    ->useAttributeAsKey('name')
420 42
                    ->isRequired()
421 42
                    ->requiresAtLeastOneElement()
422 42
                    ->prototype('scalar')->end()
423 42
                ->end()
424 42
                ->scalarNode('base_url')
425 42
                    ->defaultNull()
426 42
                    ->info('Default host name and optional path for path based invalidation.')
427 42
                ->end()
428 42
                ->scalarNode('http_client')
429 42
                    ->defaultNull()
430 42
                    ->info('Httplug async client service name to use for sending the requests.')
431 42
                ->end()
432 42
            ->end()
433
        ;
434
435 42
        return $node;
436
    }
437
438 42
    private function addTestSection(ArrayNodeDefinition $rootNode)
439
    {
440
        $rootNode
441 42
            ->children()
442 42
                ->arrayNode('test')
443 42
                    ->children()
444 42
                        ->scalarNode('cache_header')
445 42
                            ->defaultValue('X-Cache')
446 42
                            ->info('HTTP cache hit/miss header')
447 42
                        ->end()
448 42
                        ->arrayNode('proxy_server')
449 42
                            ->info('Configure how caching proxy will be run in your tests')
450 42
                            ->children()
451 42
                                ->enumNode('default')
452 42
                                    ->values(['varnish', 'nginx'])
453 42
                                    ->info('If you configure more than one proxy server, specify which client is the default.')
454 42
                                ->end()
455 42
                                ->arrayNode('varnish')
456 42
                                    ->children()
457 42
                                        ->scalarNode('config_file')->isRequired()->end()
458 42
                                        ->scalarNode('binary')->defaultValue('varnishd')->end()
459 42
                                        ->integerNode('port')->defaultValue(6181)->end()
460 42
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
461 42
                                    ->end()
462 42
                                ->end()
463 42
                                ->arrayNode('nginx')
464 42
                                    ->children()
465 42
                                        ->scalarNode('config_file')->isRequired()->end()
466 42
                                        ->scalarNode('binary')->defaultValue('nginx')->end()
467 42
                                        ->integerNode('port')->defaultValue(8080)->end()
468 42
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
469 42
                                    ->end()
470 42
                                ->end()
471 42
                            ->end()
472 42
                        ->end()
473 42
                    ->end()
474 42
                ->end()
475 42
            ->end();
476 42
    }
477
478
    /**
479
     * Cache manager main section.
480
     *
481
     * @param ArrayNodeDefinition $rootNode
482
     */
483 42
    private function addCacheManagerSection(ArrayNodeDefinition $rootNode)
484
    {
485
        $rootNode
486 42
            ->children()
487 42
                ->arrayNode('cache_manager')
488 42
                    ->addDefaultsIfNotSet()
489 42
                    ->beforeNormalization()
490 42
                        ->ifArray()
491 42
                        ->then(function ($v) {
492 6
                            $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
493
494 6
                            return $v;
495 42
                        })
496 42
                    ->end()
497 42
                    ->info('Configure the cache manager. Needs a proxy_client to be configured.')
498 42
                    ->children()
499 42
                        ->enumNode('enabled')
500 42
                            ->values([true, false, 'auto'])
501 42
                            ->defaultValue('auto')
502 42
                            ->info('Allows to disable the invalidation manager. Enabled by default if you configure a proxy client.')
503 42
                        ->end()
504 42
                        ->scalarNode('custom_proxy_client')
505 42
                            ->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.')
506 42
                            ->cannotBeEmpty()
507 42
                        ->end()
508 42
                        ->enumNode('generate_url_type')
509 42
                            ->values([
510 42
                                'auto',
511
                                UrlGeneratorInterface::ABSOLUTE_PATH,
512
                                UrlGeneratorInterface::ABSOLUTE_URL,
513
                                UrlGeneratorInterface::NETWORK_PATH,
514
                                UrlGeneratorInterface::RELATIVE_PATH,
515
                            ])
516 42
                            ->defaultValue('auto')
517 42
                            ->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.')
518 42
                        ->end()
519 42
                    ->end()
520
        ;
521 42
    }
522
523 42
    private function addTagSection(ArrayNodeDefinition $rootNode)
524
    {
525
        $rules = $rootNode
526 42
            ->children()
527 42
                ->arrayNode('tags')
528 42
                    ->addDefaultsIfNotSet()
529 42
                    ->fixXmlConfig('rule')
530 42
                    ->children()
531 42
                        ->enumNode('enabled')
532 42
                            ->values([true, false, 'auto'])
533 42
                            ->defaultValue('auto')
534 42
                            ->info('Allows to disable the event subscriber for tag configuration and annotations when your project does not use the annotations. Enabled by default if you configured the cache manager.')
535 42
                        ->end()
536 42
                        ->booleanNode('strict')->defaultFalse()->end()
537 42
                        ->scalarNode('expression_language')
538 42
                            ->defaultNull()
539 42
                            ->info('Service name of a custom ExpressionLanugage to use.')
540 42
                        ->end()
541 42
                        ->scalarNode('response_header')
542 42
                            ->defaultValue(TagHeaderFormatter::DEFAULT_HEADER_NAME)
543 42
                            ->info('HTTP header that contains cache tags')
544 42
                        ->end()
545 42
                        ->arrayNode('rules')
546 42
                            ->prototype('array')
547 42
                                ->fixXmlConfig('tag')
548 42
                                ->fixXmlConfig('tag_expression')
549 42
                                ->validate()
550 42
                                    ->ifTrue(function ($v) {
551 4
                                        return !empty($v['tag_expressions']) && !class_exists(ExpressionLanguage::class);
552 42
                                    })
553 42
                                    ->thenInvalid('Configured a tag_expression but ExpressionLanugage is not available')
554 42
                                ->end()
555 42
                                ->children();
556
557 42
        $this->addMatch($rules);
558
559
        $rules
560 42
            ->arrayNode('tags')
561 42
                ->prototype('scalar')
562 42
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
563 42
            ->end()->end()
564 42
            ->arrayNode('tag_expressions')
565 42
                ->prototype('scalar')
566 42
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
567 42
            ->end()
568
        ;
569 42
    }
570
571 42
    private function addInvalidationSection(ArrayNodeDefinition $rootNode)
572
    {
573
        $rules = $rootNode
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method fixXmlConfig() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
574 42
            ->children()
575 42
                ->arrayNode('invalidation')
576 42
                    ->fixXmlConfig('rule')
577 42
                    ->addDefaultsIfNotSet()
578 42
                    ->children()
579 42
                        ->enumNode('enabled')
580 42
                            ->values([true, false, 'auto'])
581 42
                            ->defaultValue('auto')
582 42
                            ->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.')
583 42
                        ->end()
584 42
                        ->scalarNode('expression_language')
585 42
                            ->defaultNull()
586 42
                            ->info('Service name of a custom ExpressionLanugage to use.')
587 42
                        ->end()
588 42
                        ->arrayNode('rules')
589 42
                            ->info('Set what requests should invalidate which target routes.')
590 42
                            ->prototype('array')
591 42
                                ->fixXmlConfig('route')
592 42
                                ->children();
593
594 42
        $this->addMatch($rules);
595
        $rules
596 42
            ->arrayNode('routes')
597 42
                ->isRequired()
598 42
                ->requiresAtLeastOneElement()
599 42
                ->useAttributeAsKey('name')
600 42
                ->info('Target routes to invalidate when request is matched')
601 42
                ->prototype('array')
602 42
                    ->children()
603 42
                        ->booleanNode('ignore_extra_params')->defaultTrue()->end()
604 42
                    ->end()
605 42
                ->end()
606 42
            ->end();
607 42
    }
608
609
    /**
610
     * User context main section.
611
     *
612
     * @param ArrayNodeDefinition $rootNode
613
     */
614 42
    private function addUserContextListenerSection(ArrayNodeDefinition $rootNode)
615
    {
616
        $rootNode
617 42
            ->children()
618 42
                ->arrayNode('user_context')
619 42
                    ->info('Listener that returns the request for the user context hash as early as possible.')
620 42
                    ->addDefaultsIfNotSet()
621 42
                    ->canBeEnabled()
622 42
                    ->fixXmlConfig('user_identifier_header')
623 42
                    ->children()
624 42
                        ->arrayNode('match')
625 42
                            ->addDefaultsIfNotSet()
626 42
                            ->children()
627 42
                                ->scalarNode('matcher_service')
628 42
                                    ->defaultValue('fos_http_cache.user_context.request_matcher')
629 42
                                    ->info('Service id of a request matcher that tells whether the request is a context hash request.')
630 42
                                ->end()
631 42
                                ->scalarNode('accept')
632 42
                                    ->defaultValue('application/vnd.fos.user-context-hash')
633 42
                                    ->info('Specify the accept HTTP header used for context hash requests.')
634 42
                                ->end()
635 42
                                ->scalarNode('method')
636 42
                                    ->defaultNull()
637 42
                                    ->info('Specify the HTTP method used for context hash requests.')
638 42
                                ->end()
639 42
                            ->end()
640 42
                        ->end()
641 42
                        ->scalarNode('hash_cache_ttl')
642 42
                            ->defaultValue(0)
643 42
                            ->info('Cache the response for the hash for the specified number of seconds. Setting this to 0 will not cache those responses at all.')
644 42
                        ->end()
645 42
                        ->booleanNode('always_vary_on_context_hash')
646 42
                            ->defaultTrue()
647 42
                            ->info('Whether to always add the user context hash header name in the response Vary header.')
648 42
                        ->end()
649 42
                        ->arrayNode('user_identifier_headers')
650 42
                            ->prototype('scalar')->end()
651 42
                            ->defaultValue(['Cookie', 'Authorization'])
652 42
                            ->info('List of headers that contains the unique identifier for the user in the hash request.')
653 42
                        ->end()
654 42
                        ->scalarNode('user_hash_header')
655 42
                            ->defaultValue('X-User-Context-Hash')
656 42
                            ->info('Name of the header that contains the hash information for the context.')
657 42
                        ->end()
658 42
                        ->booleanNode('role_provider')
659 42
                            ->defaultFalse()
660 42
                            ->info('Whether to enable a provider that automatically adds all roles of the current user to the context.')
661 42
                        ->end()
662 42
                        ->arrayNode('logout_handler')
663 42
                            ->addDefaultsIfNotSet()
664 42
                            ->canBeEnabled()
665 42
                            ->children()
666 42
                                ->enumNode('enabled')
667 42
                                    ->values([true, false, 'auto'])
668 42
                                    ->defaultValue('auto')
669 42
                                    ->info('Whether to enable the user context logout handler.')
670 42
                                ->end()
671 42
                            ->end()
672 42
                        ->end()
673 42
                    ->end()
674 42
                ->end()
675 42
            ->end()
676
        ;
677 42
    }
678
679 42
    private function addFlashMessageSection(ArrayNodeDefinition $rootNode)
680
    {
681
        $rootNode
682 42
            ->children()
683 42
                ->arrayNode('flash_message')
684 42
                    ->canBeUnset()
685 42
                    ->canBeEnabled()
686 42
                    ->info('Activate the flash message listener that puts flash messages into a cookie.')
687 42
                    ->children()
688 42
                        ->scalarNode('name')
689 42
                            ->defaultValue('flashes')
690 42
                            ->info('Name of the cookie to set for flashes.')
691 42
                        ->end()
692 42
                        ->scalarNode('path')
693 42
                            ->defaultValue('/')
694 42
                            ->info('Cookie path validity.')
695 42
                        ->end()
696 42
                        ->scalarNode('host')
697 42
                            ->defaultNull()
698 42
                            ->info('Cookie host name validity.')
699 42
                        ->end()
700 42
                        ->scalarNode('secure')
701 42
                            ->defaultFalse()
702 42
                            ->info('Whether the cookie should only be transmitted over a secure HTTPS connection from the client.')
703 42
                        ->end()
704 42
                    ->end()
705 42
                ->end()
706 42
            ->end();
707 42
    }
708
709 42
    private function addDebugSection(ArrayNodeDefinition $rootNode)
710
    {
711
        $rootNode
712 42
            ->children()
713 42
                ->arrayNode('debug')
714 42
                ->addDefaultsIfNotSet()
715 42
                ->canBeEnabled()
716 42
                ->children()
717 42
                    ->booleanNode('enabled')
718 42
                        ->defaultValue($this->debug)
719 42
                        ->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.')
720 42
                    ->end()
721 42
                    ->scalarNode('header')
722 42
                        ->defaultValue('X-Cache-Debug')
723 42
                        ->info('The header to send if debug is true.')
724 42
                    ->end()
725 42
                ->end()
726 42
            ->end()
727 42
        ->end();
728 42
    }
729
}
730