Completed
Push — master ( 380539...8dc005 )
by David
11:18
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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