Completed
Push — master ( 6b75eb...2904a3 )
by David
16:55
created

Configuration   C

Complexity

Total Complexity 38

Size/Duplication

Total Lines 724
Duplicated Lines 4.97 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 99.66%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 38
c 1
b 1
f 0
lcom 1
cbo 9
dl 36
loc 724
ccs 579
cts 581
cp 0.9966
rs 6.4615

14 Methods

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

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\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 45
    public function __construct($debug)
47
    {
48 45
        $this->debug = $debug;
49 45
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 45
    public function getConfigTreeBuilder()
55
    {
56 45
        $treeBuilder = new TreeBuilder();
57 45
        $rootNode = $treeBuilder->root('fos_http_cache');
58
59
        $rootNode
60 45
            ->validate()
61 45
                ->ifTrue(function ($v) {
62 43
                    return $v['cache_manager']['enabled']
63 43
                        && !isset($v['proxy_client'])
64 43
                        && !isset($v['cache_manager']['custom_proxy_client'])
65
                    ;
66 45
                })
67 45 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

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

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

Loading history...
68 15
                    if ('auto' === $v['cache_manager']['enabled']) {
69 14
                        $v['cache_manager']['enabled'] = false;
70
71 14
                        return $v;
72
                    }
73
74 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client or specify a custom_proxy_client to use the cache_manager.');
75 45
                })
76 45
            ->end()
77 45
            ->validate()
78 45
                ->ifTrue(function ($v) {
79 42
                    return $v['tags']['enabled'] && !$v['cache_manager']['enabled'];
80 45
                })
81 45 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

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

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

Loading history...
82 16
                    if ('auto' === $v['tags']['enabled']) {
83 15
                        $v['tags']['enabled'] = false;
84
85 15
                        return $v;
86
                    }
87
88 1
                    throw new InvalidConfigurationException('You need to configure a proxy_client to get the cache_manager needed for tag handling.');
89 45
                })
90 45
            ->end()
91 45
            ->validate()
92 45
                ->ifTrue(function ($v) {
93 41
                    return $v['invalidation']['enabled'] && !$v['cache_manager']['enabled'];
94 45
                })
95 45 View Code Duplication
                ->then(function ($v) {
0 ignored issues
show
Duplication introduced by David Buchmann
This code seems to be duplicated across your project.

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

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

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