Completed
Pull Request — master (#440)
by Yanick
35:04
created

Configuration::addProxyClientSection()   B

Complexity

Conditions 2
Paths 1

Size

Total Lines 79
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 67
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 79
ccs 67
cts 67
cp 1
rs 8.8701
cc 2
eloc 70
nc 1
nop 1
crap 2

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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