Completed
Push — master ( ab38a7...a2326e )
by David
10s
created

src/DependencyInjection/Configuration.php (2 issues)

Upgrade to new PHP Analysis Engine

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

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