Completed
Push — master ( 9a4875...187362 )
by David
05:27
created

Configuration   B

Complexity

Total Complexity 30

Size/Duplication

Total Lines 693
Duplicated Lines 5.19 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 99.82%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 30
lcom 1
cbo 9
dl 36
loc 693
ccs 542
cts 543
cp 0.9982
rs 7.7221
c 2
b 1
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B getHttpDispatcherNode() 0 29 1
B addTestSection() 0 39 1
A addTagSection() 0 47 2
B addInvalidationSection() 0 37 1
A addUserContextListenerSection() 0 64 1
B addFlashMessageSection() 0 29 1
A addDebugSection() 0 20 1
B addCacheableResponseSection() 0 30 2
B addCacheManagerSection() 0 39 2
B addCacheControlSection() 0 82 2
A addMatch() 0 63 3
A addProxyClientSection() 0 75 2
C getConfigTreeBuilder() 36 82 10

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 41
    public function __construct($debug)
48
    {
49 41
        $this->debug = $debug;
50 41
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 41
    public function getConfigTreeBuilder()
56
    {
57 41
        $treeBuilder = new TreeBuilder();
58 41
        $rootNode = $treeBuilder->root('fos_http_cache');
59
60
        $rootNode
61 41
            ->validate()
62
                ->ifTrue(function ($v) {
63 40
                    return $v['cache_manager']['enabled']
64 40
                        && !isset($v['proxy_client'])
65 40
                        && !isset($v['cache_manager']['custom_proxy_client'])
66
                    ;
67 41
                })
68 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 13
                    if ('auto' === $v['cache_manager']['enabled']) {
70 12
                        $v['cache_manager']['enabled'] = false;
71
72 12
                        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 41
                })
77 41
            ->end()
78 41
            ->validate()
79
                ->ifTrue(function ($v) {
80 39
                    return $v['tags']['enabled'] && !$v['cache_manager']['enabled'];
81 41
                })
82 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 14
                    if ('auto' === $v['tags']['enabled']) {
84 13
                        $v['tags']['enabled'] = false;
85
86 13
                        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 41
                })
91 41
            ->end()
92 41
            ->validate()
93
                ->ifTrue(function ($v) {
94 38
                    return $v['invalidation']['enabled'] && !$v['cache_manager']['enabled'];
95 41
                })
96 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 13
                    if ('auto' === $v['invalidation']['enabled']) {
98 12
                        $v['invalidation']['enabled'] = false;
99
100 12
                        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 41
                })
105 41
            ->end()
106 41
            ->validate()
107 41
                ->ifTrue(
108
                    function ($v) {
109 37
                        return $v['user_context']['logout_handler']['enabled']
110 37
                            && !isset($v['proxy_client']);
111 41
                    }
112
                )
113 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 14
                    if ('auto' === $v['user_context']['logout_handler']['enabled']) {
115 14
                        $v['user_context']['logout_handler']['enabled'] = false;
116
117 14
                        return $v;
118
                    }
119
120
                    throw new InvalidConfigurationException('You need to configure a proxy_client for the logout_handler.');
121 41
                })
122
        ;
123
124 41
        $this->addCacheableResponseSection($rootNode);
125 41
        $this->addCacheControlSection($rootNode);
126 41
        $this->addProxyClientSection($rootNode);
127 41
        $this->addCacheManagerSection($rootNode);
128 41
        $this->addTagSection($rootNode);
129 41
        $this->addInvalidationSection($rootNode);
130 41
        $this->addUserContextListenerSection($rootNode);
131 41
        $this->addFlashMessageSection($rootNode);
132 41
        $this->addTestSection($rootNode);
133 41
        $this->addDebugSection($rootNode);
134
135 41
        return $treeBuilder;
136
    }
137
138 41
    private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
139
    {
140
        $rootNode
141 41
            ->children()
142 41
                ->arrayNode('cacheable')
143 41
                    ->addDefaultsIfNotSet()
144 41
                    ->children()
145 41
                        ->arrayNode('response')
146 41
                            ->addDefaultsIfNotSet()
147 41
                            ->children()
148 41
                                ->arrayNode('additional_status')
149 41
                                    ->prototype('scalar')->end()
150 41
                                    ->info('Additional response HTTP status codes that will be considered cacheable.')
151 41
                                ->end()
152 41
                                ->scalarNode('expression')
153 41
                                    ->defaultNull()
154 41
                                    ->info('Expression to decide whether response is cacheable. Replaces the default status codes.')
155 41
                            ->end()
156 41
                        ->end()
157
158 41
                        ->validate()
159
                            ->ifTrue(function ($v) {
160 4
                                return !empty($v['additional_status']) && !empty($v['expression']);
161 41
                            })
162 41
                            ->thenInvalid('You may not set both additional_status and expression.')
163 41
                        ->end()
164 41
                    ->end()
165 41
                ->end()
166 41
            ->end();
167 41
    }
168
169
    /**
170
     * Cache header control main section.
171
     *
172
     * @param ArrayNodeDefinition $rootNode
173
     */
174 41
    private function addCacheControlSection(ArrayNodeDefinition $rootNode)
175
    {
176
        $rules = $rootNode
177 41
            ->children()
178 41
                ->arrayNode('cache_control')
179 41
                    ->fixXmlConfig('rule')
180 41
                    ->children()
181 41
                        ->arrayNode('defaults')
182 41
                            ->addDefaultsIfNotSet()
183 41
                            ->children()
184 41
                                ->booleanNode('overwrite')
185 41
                                    ->info('Whether to overwrite existing cache headers')
186 41
                                    ->defaultFalse()
187 41
                                ->end()
188 41
                            ->end()
189 41
                        ->end()
190 41
                        ->arrayNode('rules')
191 41
                            ->prototype('array')
192 41
                                ->children();
193
194 41
        $this->addMatch($rules, true);
195
        $rules
196 41
            ->arrayNode('headers')
197 41
                ->isRequired()
198
                // todo validate there is some header defined
199 41
                ->children()
200 41
                    ->enumNode('overwrite')
201 41
                        ->info('Whether to overwrite cache headers for this rule, defaults to the cache_control.defaults.overwrite setting')
202 41
                        ->values(['default', true, false])
203 41
                        ->defaultValue('default')
204 41
                    ->end()
205 41
                    ->arrayNode('cache_control')
206 41
                        ->info('Add the specified cache control directives.')
207 41
                        ->children()
208 41
                            ->scalarNode('max_age')->end()
209 41
                            ->scalarNode('s_maxage')->end()
210 41
                            ->booleanNode('private')->end()
211 41
                            ->booleanNode('public')->end()
212 41
                            ->booleanNode('must_revalidate')->end()
213 41
                            ->booleanNode('proxy_revalidate')->end()
214 41
                            ->booleanNode('no_transform')->end()
215 41
                            ->booleanNode('no_cache')->end()
216 41
                            ->booleanNode('no_store')->end()
217 41
                            ->scalarNode('stale_if_error')->end()
218 41
                            ->scalarNode('stale_while_revalidate')->end()
219 41
                        ->end()
220 41
                    ->end()
221 41
                    ->enumNode('etag')
222 41
                        ->defaultValue(false)
223 41
                        ->treatTrueLike('strong')
224 41
                        ->info('Set a simple ETag which is just the md5 hash of the response body. '.
225 41
                               'You can specify which type of ETag you want by passing "strong" or "weak".')
226 41
                        ->values(['weak', 'strong', false])
227 41
                    ->end()
228 41
                    ->scalarNode('last_modified')
229 41
                        ->validate()
230
                            ->ifTrue(function ($v) {
231 2
                                if (is_string($v)) {
232 2
                                    new \DateTime($v);
233
                                }
234
235 1
                                return false;
236 41
                            })
237 41
                            ->thenInvalid('') // this will never happen as new DateTime will throw an exception if $v is no date
238 41
                        ->end()
239 41
                        ->info('Set a default last modified timestamp if none is set yet. Value must be parseable by DateTime')
240 41
                    ->end()
241 41
                    ->scalarNode('reverse_proxy_ttl')
242 41
                        ->defaultNull()
243 41
                        ->info('Specify an X-Reverse-Proxy-TTL header with a time in seconds for a caching proxy under your control.')
244 41
                    ->end()
245 41
                    ->arrayNode('vary')
246
                        ->beforeNormalization()->ifString()->then(function ($v) {
247 2
                            return preg_split('/\s*,\s*/', $v);
248 41
                        })->end()
249 41
                        ->prototype('scalar')->end()
250 41
                        ->info('Define a list of additional headers on which the response varies.')
251 41
                    ->end()
252 41
                ->end()
253 41
            ->end()
254
        ;
255 41
    }
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 41
    private function addMatch(NodeBuilder $rules, $matchResponse = false)
264
    {
265
        $match = $rules
266 41
            ->arrayNode('match')
267 41
                ->cannotBeOverwritten()
268 41
                ->isRequired()
269 41
                ->fixXmlConfig('method')
270 41
                ->fixXmlConfig('ip')
271 41
                ->fixXmlConfig('attribute')
272 41
                ->validate()
273
                    ->ifTrue(function ($v) {
274 14
                        return !empty($v['additional_response_status']) && !empty($v['match_response']);
275 41
                    })
276 41
                    ->thenInvalid('You may not set both additional_response_status and match_response.')
277 41
                ->end()
278 41
                ->children()
279 41
                    ->scalarNode('path')
280 41
                        ->defaultNull()
281 41
                        ->info('Request path.')
282 41
                    ->end()
283 41
                    ->scalarNode('query_string')
284 41
                        ->defaultNull()
285 41
                        ->info('Request query string.')
286 41
                    ->end()
287 41
                    ->scalarNode('host')
288 41
                        ->defaultNull()
289 41
                        ->info('Request host name.')
290 41
                    ->end()
291 41
                    ->arrayNode('methods')
292
                        ->beforeNormalization()->ifString()->then(function ($v) {
293 3
                            return preg_split('/\s*,\s*/', $v);
294 41
                        })->end()
295 41
                        ->useAttributeAsKey('name')
296 41
                        ->prototype('scalar')->end()
297 41
                        ->info('Request HTTP methods.')
298 41
                    ->end()
299 41
                    ->arrayNode('ips')
300
                        ->beforeNormalization()->ifString()->then(function ($v) {
301 3
                            return preg_split('/\s*,\s*/', $v);
302 41
                        })->end()
303 41
                        ->useAttributeAsKey('name')
304 41
                        ->prototype('scalar')->end()
305 41
                        ->info('List of client IPs.')
306 41
                    ->end()
307 41
                    ->arrayNode('attributes')
308 41
                        ->useAttributeAsKey('name')
309 41
                        ->prototype('scalar')->end()
310 41
                        ->info('Regular expressions on request attributes.')
311 41
                    ->end()
312
        ;
313 41
        if ($matchResponse) {
314
            $match
315 41
                ->arrayNode('additional_response_status')
316 41
                    ->prototype('scalar')->end()
317 41
                    ->info('Additional response HTTP status codes that will match. Replaces cacheable configuration.')
318 41
                ->end()
319 41
                ->scalarNode('match_response')
320 41
                    ->defaultNull()
321 41
                    ->info('Expression to decide whether response should be matched. Replaces cacheable configuration.')
322 41
                ->end()
323
            ;
324
        }
325 41
    }
326
327 41
    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 41
            ->children()
331 41
                ->arrayNode('proxy_client')
332 41
                    ->children()
333 41
                        ->enumNode('default')
334 41
                            ->values(['varnish', 'nginx', 'symfony', 'noop'])
335 41
                            ->info('If you configure more than one proxy client, you need to specify which client is the default.')
336 41
                        ->end()
337 41
                        ->arrayNode('varnish')
338 41
                            ->fixXmlConfig('default_ban_header')
339 41
                            ->validate()
340
                                ->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 41
                                })
347 41
                            ->end()
348 41
                            ->children()
349 41
                                ->scalarNode('tags_header')
350 41
                                    ->defaultValue(Varnish::DEFAULT_HTTP_HEADER_CACHE_TAGS)
351 41
                                    ->info('HTTP header to use when sending tag invalidation requests to Varnish')
352 41
                                ->end()
353 41
                                ->scalarNode('header_length')
354 41
                                    ->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 41
                                ->end()
356 41
                                ->arrayNode('default_ban_headers')
357 41
                                    ->useAttributeAsKey('name')
358 41
                                    ->info('Map of additional headers to include in each ban request.')
359 41
                                    ->prototype('scalar')->end()
360 41
                                ->end()
361 41
                                ->append($this->getHttpDispatcherNode())
362 41
                            ->end()
363 41
                        ->end()
364
365 41
                        ->arrayNode('nginx')
366 41
                            ->children()
367 41
                                ->scalarNode('purge_location')
368 41
                                    ->defaultValue(false)
369 41
                                    ->info('Path to trigger the purge on Nginx for different location purge.')
370 41
                                ->end()
371 41
                                ->append($this->getHttpDispatcherNode())
372 41
                            ->end()
373 41
                        ->end()
374
375 41
                        ->arrayNode('symfony')
376 41
                            ->children()
377 41
                                ->scalarNode('tags_header')
378 41
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_HEADER)
379 41
                                    ->info('HTTP header to use when sending tag invalidation requests to Symfony HttpCache')
380 41
                                ->end()
381 41
                                ->scalarNode('tags_method')
382 41
                                    ->defaultValue(PurgeTagsListener::DEFAULT_TAGS_METHOD)
383 41
                                    ->info('HTTP method for sending tag invalidation requests to Symfony HttpCache')
384 41
                                ->end()
385 41
                                ->scalarNode('header_length')
386 41
                                    ->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 41
                                ->end()
388 41
                                ->scalarNode('purge_method')
389 41
                                    ->defaultValue(PurgeListener::DEFAULT_PURGE_METHOD)
390 41
                                    ->info('HTTP method to use when sending purge requests to Symfony HttpCache')
391 41
                                ->end()
392 41
                                ->append($this->getHttpDispatcherNode())
393 41
                            ->end()
394 41
                        ->end()
395
396 41
                        ->booleanNode('noop')->end()
397
398 41
                    ->end()
399 41
                ->end()
400 41
            ->end();
401 41
    }
402
403
    /**
404
     * Get the configuration node for a HTTP dispatcher in a proxy client.
405
     *
406
     * @return NodeDefinition
407
     */
408 41
    private function getHttpDispatcherNode()
409
    {
410 41
        $treeBuilder = new TreeBuilder();
411 41
        $node = $treeBuilder->root('http');
412
413
        $node
414 41
            ->fixXmlConfig('server')
415 41
            ->isRequired()
416 41
            ->children()
417 41
                ->arrayNode('servers')
418 41
                    ->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 41
                    ->useAttributeAsKey('name')
420 41
                    ->isRequired()
421 41
                    ->requiresAtLeastOneElement()
422 41
                    ->prototype('scalar')->end()
423 41
                ->end()
424 41
                ->scalarNode('base_url')
425 41
                    ->defaultNull()
426 41
                    ->info('Default host name and optional path for path based invalidation.')
427 41
                ->end()
428 41
                ->scalarNode('http_client')
429 41
                    ->defaultNull()
430 41
                    ->info('Httplug async client service name to use for sending the requests.')
431 41
                ->end()
432 41
            ->end()
433
        ;
434
435 41
        return $node;
436
    }
437
438 41
    private function addTestSection(ArrayNodeDefinition $rootNode)
439
    {
440
        $rootNode
441 41
            ->children()
442 41
                ->arrayNode('test')
443 41
                    ->children()
444 41
                        ->scalarNode('cache_header')
445 41
                            ->defaultValue('X-Cache')
446 41
                            ->info('HTTP cache hit/miss header')
447 41
                        ->end()
448 41
                        ->arrayNode('proxy_server')
449 41
                            ->info('Configure how caching proxy will be run in your tests')
450 41
                            ->children()
451 41
                                ->enumNode('default')
452 41
                                    ->values(['varnish', 'nginx'])
453 41
                                    ->info('If you configure more than one proxy server, specify which client is the default.')
454 41
                                ->end()
455 41
                                ->arrayNode('varnish')
456 41
                                    ->children()
457 41
                                        ->scalarNode('config_file')->isRequired()->end()
458 41
                                        ->scalarNode('binary')->defaultValue('varnishd')->end()
459 41
                                        ->integerNode('port')->defaultValue(6181)->end()
460 41
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
461 41
                                    ->end()
462 41
                                ->end()
463 41
                                ->arrayNode('nginx')
464 41
                                    ->children()
465 41
                                        ->scalarNode('config_file')->isRequired()->end()
466 41
                                        ->scalarNode('binary')->defaultValue('nginx')->end()
467 41
                                        ->integerNode('port')->defaultValue(8080)->end()
468 41
                                        ->scalarNode('ip')->defaultValue('127.0.0.1')->end()
469 41
                                    ->end()
470 41
                                ->end()
471 41
                            ->end()
472 41
                        ->end()
473 41
                    ->end()
474 41
                ->end()
475 41
            ->end();
476 41
    }
477
478
    /**
479
     * Cache manager main section.
480
     *
481
     * @param ArrayNodeDefinition $rootNode
482
     */
483 41
    private function addCacheManagerSection(ArrayNodeDefinition $rootNode)
484
    {
485
        $rootNode
486 41
            ->children()
487 41
                ->arrayNode('cache_manager')
488 41
                    ->addDefaultsIfNotSet()
489 41
                    ->beforeNormalization()
490 41
                        ->ifArray()
491
                        ->then(function ($v) {
492 6
                            $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
493
494 6
                            return $v;
495 41
                        })
496 41
                    ->end()
497 41
                    ->info('Configure the cache manager. Needs a proxy_client to be configured.')
498 41
                    ->children()
499 41
                        ->enumNode('enabled')
500 41
                            ->values([true, false, 'auto'])
501 41
                            ->defaultValue('auto')
502 41
                            ->info('Allows to disable the invalidation manager. Enabled by default if you configure a proxy client.')
503 41
                        ->end()
504 41
                        ->scalarNode('custom_proxy_client')
505 41
                            ->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 41
                            ->cannotBeEmpty()
507 41
                        ->end()
508 41
                        ->enumNode('generate_url_type')
509 41
                            ->values([
510 41
                                'auto',
511
                                UrlGeneratorInterface::ABSOLUTE_PATH,
512
                                UrlGeneratorInterface::ABSOLUTE_URL,
513
                                UrlGeneratorInterface::NETWORK_PATH,
514
                                UrlGeneratorInterface::RELATIVE_PATH,
515
                            ])
516 41
                            ->defaultValue('auto')
517 41
                            ->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 41
                        ->end()
519 41
                    ->end()
520
        ;
521 41
    }
522
523 41
    private function addTagSection(ArrayNodeDefinition $rootNode)
524
    {
525
        $rules = $rootNode
526 41
            ->children()
527 41
                ->arrayNode('tags')
528 41
                    ->addDefaultsIfNotSet()
529 41
                    ->fixXmlConfig('rule')
530 41
                    ->children()
531 41
                        ->enumNode('enabled')
532 41
                            ->values([true, false, 'auto'])
533 41
                            ->defaultValue('auto')
534 41
                            ->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 41
                        ->end()
536 41
                        ->booleanNode('strict')->defaultFalse()->end()
537 41
                        ->scalarNode('expression_language')
538 41
                            ->defaultNull()
539 41
                            ->info('Service name of a custom ExpressionLanugage to use.')
540 41
                        ->end()
541 41
                        ->scalarNode('response_header')
542 41
                            ->defaultValue(TagHeaderFormatter::DEFAULT_HEADER_NAME)
543 41
                            ->info('HTTP header that contains cache tags')
544 41
                        ->end()
545 41
                        ->arrayNode('rules')
546 41
                            ->prototype('array')
547 41
                                ->fixXmlConfig('tag')
548 41
                                ->fixXmlConfig('tag_expression')
549 41
                                ->validate()
550 41
                                    ->ifTrue(function ($v) {
551 4
                                        return !empty($v['tag_expressions']) && !class_exists(ExpressionLanguage::class);
552 41
                                    })
553 41
                                    ->thenInvalid('Configured a tag_expression but ExpressionLanugage is not available')
554 41
                                ->end()
555 41
                                ->children();
556
557 41
        $this->addMatch($rules);
558
559
        $rules
560 41
            ->arrayNode('tags')
561 41
                ->prototype('scalar')
562 41
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
563 41
            ->end()->end()
564 41
            ->arrayNode('tag_expressions')
565 41
                ->prototype('scalar')
566 41
                ->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
567 41
            ->end()
568
        ;
569 41
    }
570
571 41
    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 41
            ->children()
575 41
                ->arrayNode('invalidation')
576 41
                    ->fixXmlConfig('rule')
577 41
                    ->addDefaultsIfNotSet()
578 41
                    ->children()
579 41
                        ->enumNode('enabled')
580 41
                            ->values([true, false, 'auto'])
581 41
                            ->defaultValue('auto')
582 41
                            ->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 41
                        ->end()
584 41
                        ->scalarNode('expression_language')
585 41
                            ->defaultNull()
586 41
                            ->info('Service name of a custom ExpressionLanugage to use.')
587 41
                        ->end()
588 41
                        ->arrayNode('rules')
589 41
                            ->info('Set what requests should invalidate which target routes.')
590 41
                            ->prototype('array')
591 41
                                ->fixXmlConfig('route')
592 41
                                ->children();
593
594 41
        $this->addMatch($rules);
595
        $rules
596 41
            ->arrayNode('routes')
597 41
                ->isRequired()
598 41
                ->requiresAtLeastOneElement()
599 41
                ->useAttributeAsKey('name')
600 41
                ->info('Target routes to invalidate when request is matched')
601 41
                ->prototype('array')
602 41
                    ->children()
603 41
                        ->booleanNode('ignore_extra_params')->defaultTrue()->end()
604 41
                    ->end()
605 41
                ->end()
606 41
            ->end();
607 41
    }
608
609
    /**
610
     * User context main section.
611
     *
612
     * @param ArrayNodeDefinition $rootNode
613
     */
614 41
    private function addUserContextListenerSection(ArrayNodeDefinition $rootNode)
615
    {
616
        $rootNode
617 41
            ->children()
618 41
                ->arrayNode('user_context')
619 41
                    ->info('Listener that returns the request for the user context hash as early as possible.')
620 41
                    ->addDefaultsIfNotSet()
621 41
                    ->canBeEnabled()
622 41
                    ->fixXmlConfig('user_identifier_header')
623 41
                    ->children()
624 41
                        ->arrayNode('match')
625 41
                            ->addDefaultsIfNotSet()
626 41
                            ->children()
627 41
                                ->scalarNode('matcher_service')
628 41
                                    ->defaultValue('fos_http_cache.user_context.request_matcher')
629 41
                                    ->info('Service id of a request matcher that tells whether the request is a context hash request.')
630 41
                                ->end()
631 41
                                ->scalarNode('accept')
632 41
                                    ->defaultValue('application/vnd.fos.user-context-hash')
633 41
                                    ->info('Specify the accept HTTP header used for context hash requests.')
634 41
                                ->end()
635 41
                                ->scalarNode('method')
636 41
                                    ->defaultNull()
637 41
                                    ->info('Specify the HTTP method used for context hash requests.')
638 41
                                ->end()
639 41
                            ->end()
640 41
                        ->end()
641 41
                        ->scalarNode('hash_cache_ttl')
642 41
                            ->defaultValue(0)
643 41
                            ->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 41
                        ->end()
645 41
                        ->booleanNode('always_vary_on_context_hash')
646 41
                            ->defaultTrue()
647 41
                            ->info('Whether to always add the user context hash header name in the response Vary header.')
648 41
                        ->end()
649 41
                        ->arrayNode('user_identifier_headers')
650 41
                            ->prototype('scalar')->end()
651 41
                            ->defaultValue(['Cookie', 'Authorization'])
652 41
                            ->info('List of headers that contains the unique identifier for the user in the hash request.')
653 41
                        ->end()
654 41
                        ->scalarNode('user_hash_header')
655 41
                            ->defaultValue('X-User-Context-Hash')
656 41
                            ->info('Name of the header that contains the hash information for the context.')
657 41
                        ->end()
658 41
                        ->booleanNode('role_provider')
659 41
                            ->defaultFalse()
660 41
                            ->info('Whether to enable a provider that automatically adds all roles of the current user to the context.')
661 41
                        ->end()
662 41
                        ->arrayNode('logout_handler')
663 41
                            ->addDefaultsIfNotSet()
664 41
                            ->canBeEnabled()
665 41
                            ->children()
666 41
                                ->enumNode('enabled')
667 41
                                    ->values([true, false, 'auto'])
668 41
                                    ->defaultValue('auto')
669 41
                                    ->info('Whether to enable the user context logout handler.')
670 41
                                ->end()
671 41
                            ->end()
672 41
                        ->end()
673 41
                    ->end()
674 41
                ->end()
675 41
            ->end()
676
        ;
677 41
    }
678
679 41
    private function addFlashMessageSection(ArrayNodeDefinition $rootNode)
680
    {
681
        $rootNode
682 41
            ->children()
683 41
                ->arrayNode('flash_message')
684 41
                    ->canBeUnset()
685 41
                    ->canBeEnabled()
686 41
                    ->info('Activate the flash message listener that puts flash messages into a cookie.')
687 41
                    ->children()
688 41
                        ->scalarNode('name')
689 41
                            ->defaultValue('flashes')
690 41
                            ->info('Name of the cookie to set for flashes.')
691 41
                        ->end()
692 41
                        ->scalarNode('path')
693 41
                            ->defaultValue('/')
694 41
                            ->info('Cookie path validity.')
695 41
                        ->end()
696 41
                        ->scalarNode('host')
697 41
                            ->defaultNull()
698 41
                            ->info('Cookie host name validity.')
699 41
                        ->end()
700 41
                        ->scalarNode('secure')
701 41
                            ->defaultFalse()
702 41
                            ->info('Whether the cookie should only be transmitted over a secure HTTPS connection from the client.')
703 41
                        ->end()
704 41
                    ->end()
705 41
                ->end()
706 41
            ->end();
707 41
    }
708
709 41
    private function addDebugSection(ArrayNodeDefinition $rootNode)
710
    {
711
        $rootNode
712 41
            ->children()
713 41
                ->arrayNode('debug')
714 41
                ->addDefaultsIfNotSet()
715 41
                ->canBeEnabled()
716 41
                ->children()
717 41
                    ->booleanNode('enabled')
718 41
                        ->defaultValue($this->debug)
719 41
                        ->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 41
                    ->end()
721 41
                    ->scalarNode('header')
722 41
                        ->defaultValue('X-Cache-Debug')
723 41
                        ->info('The header to send if debug is true.')
724 41
                    ->end()
725 41
                ->end()
726 41
            ->end()
727 41
        ->end();
728 41
    }
729
}
730