Completed
Pull Request — master (#299)
by David
67:30 queued 64:42
created

FOSHttpCacheExtension::loadCacheControl()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 14
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 7
nc 3
nop 2
crap 3
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\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass;
15
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
16
use Symfony\Component\DependencyInjection\ContainerBuilder;
17
use Symfony\Component\Config\FileLocator;
18
use Symfony\Component\DependencyInjection\DefinitionDecorator;
19
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
20
use Symfony\Component\DependencyInjection\Reference;
21
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
22
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
23
24
/**
25
 * {@inheritdoc}
26
 */
27
class FOSHttpCacheExtension extends Extension
28
{
29
    /**
30
     * {@inheritdoc}
31
     */
32 21
    public function getConfiguration(array $config, ContainerBuilder $container)
33
    {
34 21
        return new Configuration($container->getParameter('kernel.debug'));
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 21
    public function load(array $configs, ContainerBuilder $container)
41
    {
42 21
        $configuration = $this->getConfiguration($configs, $container);
43 21
        $config = $this->processConfiguration($configuration, $configs);
44
45 21
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
46 21
        $loader->load('matcher.xml');
47
48 21
        if ($config['debug']['enabled'] || (!empty($config['cache_control']))) {
49 3
            $debugHeader = $config['debug']['enabled'] ? $config['debug']['header'] : false;
50 3
            $container->setParameter($this->getAlias().'.debug_header', $debugHeader);
51 3
            $loader->load('cache_control_listener.xml');
52 3
        }
53
54 21
        if (!empty($config['cache_control'])) {
55 3
            $this->loadCacheControl($container, $config['cache_control']);
56 3
        }
57
58 21
        if (isset($config['proxy_client'])) {
59 16
            $this->loadProxyClient($container, $loader, $config['proxy_client']);
60 15
        }
61
62 20
        if (isset($config['test'])) {
63 1
            $this->loadTest($container, $loader, $config['test']);
64 1
        }
65
66 20
        if ($config['cache_manager']['enabled']) {
67 15
            if (!empty($config['cache_manager']['custom_proxy_client'])) {
68
                // overwrite the previously set alias, if a proxy client was also configured
69
                $container->setAlias(
70
                    $this->getAlias().'.default_proxy_client',
71
                    $config['cache_manager']['custom_proxy_client']
72
                );
73
            }
74 15
            if ('auto' === $config['cache_manager']['generate_url_type']) {
75 15
                $defaultClient = $this->getDefaultProxyClient($config['proxy_client']);
76 15
                $generateUrlType = empty($config['cache_manager']['custom_proxy_client']) && isset($config['proxy_client'][$defaultClient]['base_url'])
77 15
                    ? UrlGeneratorInterface::ABSOLUTE_PATH
78 15
                    : UrlGeneratorInterface::ABSOLUTE_URL
79 15
                ;
80 15
            } else {
81
                $generateUrlType = $config['cache_manager']['generate_url_type'];
82
            }
83 15
            $container->setParameter($this->getAlias().'.cache_manager.generate_url_type', $generateUrlType);
84 15
            $loader->load('cache_manager.xml');
85 15
        }
86
87 20
        if ($config['tags']['enabled']) {
88 15
            $this->loadCacheTagging(
89 15
                $container,
90 15
                $loader,
91 15
                $config['tags'],
92 15
                $this->getDefaultProxyClient($config['proxy_client'])
93 15
            );
94 14
        } else {
95 5
            $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', false);
96
        }
97
98 19
        if ($config['invalidation']['enabled']) {
99 14
            $loader->load('invalidation_listener.xml');
100
101 14
            if (!empty($config['invalidation']['expression_language'])) {
102
                $container->setAlias(
103
                    $this->getAlias().'.invalidation.expression_language',
104
                    $config['invalidation']['expression_language']
105
                );
106
            }
107
108 14
            if (!empty($config['invalidation']['rules'])) {
109 2
                $this->loadInvalidatorRules($container, $config['invalidation']['rules']);
110 2
            }
111 14
        }
112
113 19
        if ($config['user_context']['enabled']) {
114 4
            $this->loadUserContext($container, $loader, $config['user_context']);
115 4
        }
116
117 19
        if (!empty($config['flash_message']) && $config['flash_message']['enabled']) {
118 1
            $container->setParameter($this->getAlias().'.event_listener.flash_message.options', $config['flash_message']);
119
120 1
            $loader->load('flash_message.xml');
121 1
        }
122 19
    }
123
124
    /**
125
     * @param ContainerBuilder $container
126
     * @param array            $config
127
     *
128
     * @throws InvalidConfigurationException
129
     */
130 3
    private function loadCacheControl(ContainerBuilder $container, array $config)
131
    {
132 3
        $controlDefinition = $container->getDefinition($this->getAlias().'.event_listener.cache_control');
133
134 3
        foreach ($config['rules'] as $rule) {
135 3
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
136
137 3
            if ('default' === $rule['headers']['overwrite']) {
138 3
                $rule['headers']['overwrite'] = $config['defaults']['overwrite'];
139 3
            }
140
141 3
            $controlDefinition->addMethodCall('addRule', array($ruleMatcher, $rule['headers']));
142 3
        }
143 3
    }
144
145 5
    private function parseRuleMatcher(ContainerBuilder $container, array $match)
146
    {
147 5
        $match['ips'] = (empty($match['ips'])) ? null : $match['ips'];
148
149 5
        $requestMatcher = $this->createRequestMatcher(
150 5
            $container,
151 5
            $match['path'],
152 5
            $match['host'],
153 5
            $match['methods'],
154 5
            $match['ips'],
155 5
            $match['attributes']
156 5
        );
157
158 5
        $extraCriteria = array();
159 5
        foreach (array('additional_cacheable_status', 'match_response') as $extra) {
160 5
            if (isset($match[$extra])) {
161 5
                $extraCriteria[$extra] = $match[$extra];
162 5
            }
163 5
        }
164
165 5
        return $this->createRuleMatcher(
166 5
            $container,
167 5
            $requestMatcher,
168
            $extraCriteria
169 5
        );
170
    }
171
172 5
    private function createRuleMatcher(ContainerBuilder $container, Reference $requestMatcher, array $extraCriteria)
173
    {
174 5
        $arguments = array((string) $requestMatcher, $extraCriteria);
175 5
        $serialized = serialize($arguments);
176 5
        $id = $this->getAlias().'.rule_matcher.'.md5($serialized).sha1($serialized);
177
178 5 View Code Duplication
        if (!$container->hasDefinition($id)) {
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...
179
            $container
180 5
                ->setDefinition($id, new DefinitionDecorator($this->getAlias().'.rule_matcher'))
181 5
                ->replaceArgument(0, $requestMatcher)
182 5
                ->replaceArgument(1, $extraCriteria)
183
            ;
184 5
        }
185
186 5
        return new Reference($id);
187
    }
188
189 4
    private function loadUserContext(ContainerBuilder $container, XmlFileLoader $loader, array $config)
190
    {
191 4
        $loader->load('user_context.xml');
192
193 4
        $container->getDefinition($this->getAlias().'.user_context.request_matcher')
194 4
            ->replaceArgument(0, $config['match']['accept'])
195 4
            ->replaceArgument(1, $config['match']['method']);
196
197 4
        $container->getDefinition($this->getAlias().'.event_listener.user_context')
198 4
            ->replaceArgument(0, new Reference($config['match']['matcher_service']))
199 4
            ->replaceArgument(2, $config['user_identifier_headers'])
200 4
            ->replaceArgument(3, $config['user_hash_header'])
201 4
            ->replaceArgument(4, $config['hash_cache_ttl'])
202 4
            ->replaceArgument(5, $config['always_vary_on_context_hash']);
203
204 4
        if ($config['logout_handler']['enabled']) {
205 4
            $container->getDefinition($this->getAlias().'.user_context.logout_handler')
206 4
                ->replaceArgument(1, $config['user_identifier_headers'])
207 4
                ->replaceArgument(2, $config['match']['accept']);
208 4
        } else {
209
            $container->removeDefinition($this->getAlias().'.user_context.logout_handler');
210
        }
211
212 4
        if ($config['role_provider']) {
213 2
            $container->getDefinition($this->getAlias().'.user_context.role_provider')
214 2
                ->addTag(HashGeneratorPass::TAG_NAME)
215 2
                ->setAbstract(false);
216 2
        }
217 4
    }
218
219 5
    private function createRequestMatcher(ContainerBuilder $container, $path = null, $host = null, $methods = null, $ips = null, array $attributes = array())
220
    {
221 5
        $arguments = array($path, $host, $methods, $ips, $attributes);
222 5
        $serialized = serialize($arguments);
223 5
        $id = $this->getAlias().'.request_matcher.'.md5($serialized).sha1($serialized);
224
225 5 View Code Duplication
        if (!$container->hasDefinition($id)) {
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...
226
            $container
227 5
                ->setDefinition($id, new DefinitionDecorator($this->getAlias().'.request_matcher'))
228 5
                ->setArguments($arguments)
229
            ;
230 5
        }
231
232 5
        return new Reference($id);
233
    }
234
235 16
    private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loader, array $config)
236
    {
237 16
        if (isset($config['varnish'])) {
238 13
            $this->loadVarnish($container, $loader, $config['varnish']);
239 12
        }
240 15
        if (isset($config['nginx'])) {
241 2
            $this->loadNginx($container, $loader, $config['nginx']);
242 2
        }
243 15
        if (isset($config['symfony'])) {
244 1
            $this->loadSymfony($container, $loader, $config['symfony']);
245 1
        }
246
247 15
        $container->setAlias(
248 15
            $this->getAlias().'.default_proxy_client',
249 15
            $this->getAlias().'.proxy_client.'.$this->getDefaultProxyClient($config)
250 15
        );
251 15
    }
252
253 13 View Code Duplication
    private function loadVarnish(ContainerBuilder $container, XmlFileLoader $loader, array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
254
    {
255 13
        $loader->load('varnish.xml');
256 13
        foreach ($config['servers'] as $url) {
257 13
            $this->validateUrl($url, 'Not a valid Varnish server address: "%s"');
258 13
        }
259 13
        if (!empty($config['base_url'])) {
260 13
            $baseUrl = $this->prefixSchema($config['base_url'], 'Not a valid base path: "%s"');
0 ignored issues
show
Unused Code introduced by
The call to FOSHttpCacheExtension::prefixSchema() has too many arguments starting with 'Not a valid base path: "%s"'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
261 13
            $this->validateUrl($baseUrl, 'Not a valid base path: "%s"');
262 12
        } else {
263
            $baseUrl = null;
264
        }
265 12
        $container->setParameter($this->getAlias().'.proxy_client.varnish.servers', $config['servers']);
266 12
        $container->setParameter($this->getAlias().'.proxy_client.varnish.base_url', $baseUrl);
267
268 12
        if (!empty($config['guzzle_client'])) {
269 1
            $container->setAlias(
270 1
                $this->getAlias().'.proxy_client.varnish.guzzle_client',
271 1
                $config['guzzle_client']
272 1
            );
273 1
        }
274 12
    }
275
276 2 View Code Duplication
    private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
277
    {
278 2
        $loader->load('nginx.xml');
279 2
        foreach ($config['servers'] as $url) {
280 2
            $this->validateUrl($url, 'Not a valid Nginx server address: "%s"');
281 2
        }
282 2
        if (!empty($config['base_url'])) {
283 2
            $baseUrl = $this->prefixSchema($config['base_url'], 'Not a valid base path: "%s"');
0 ignored issues
show
Unused Code introduced by
The call to FOSHttpCacheExtension::prefixSchema() has too many arguments starting with 'Not a valid base path: "%s"'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
284 2
        } else {
285
            $baseUrl = null;
286
        }
287 2
        $container->setParameter($this->getAlias().'.proxy_client.nginx.servers', $config['servers']);
288 2
        $container->setParameter($this->getAlias().'.proxy_client.nginx.base_url', $baseUrl);
289 2
        $container->setParameter($this->getAlias().'.proxy_client.nginx.purge_location', $config['purge_location']);
290
291 2
        if (!empty($config['guzzle_client'])) {
292
            $container->setAlias(
293
                $this->getAlias().'.proxy_client.nginx.guzzle_client',
294
                $config['guzzle_client']
295
            );
296
        }
297 2
    }
298
299 1 View Code Duplication
    private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
300
    {
301 1
        $loader->load('symfony.xml');
302 1
        foreach ($config['servers'] as $url) {
303 1
            $this->validateUrl($url, 'Not a valid web server address: "%s"');
304 1
        }
305 1
        if (!empty($config['base_url'])) {
306 1
            $baseUrl = $this->prefixSchema($config['base_url'], 'Not a valid base path: "%s"');
0 ignored issues
show
Unused Code introduced by
The call to FOSHttpCacheExtension::prefixSchema() has too many arguments starting with 'Not a valid base path: "%s"'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
307 1
            $this->validateUrl($baseUrl, 'Not a valid base path: "%s"');
308 1
        } else {
309
            $baseUrl = null;
310
        }
311 1
        $container->setParameter($this->getAlias().'.proxy_client.symfony.servers', $config['servers']);
312 1
        $container->setParameter($this->getAlias().'.proxy_client.symfony.base_url', $baseUrl);
313
314 1
        if (!empty($config['guzzle_client'])) {
315
            $container->setAlias(
316
                $this->getAlias().'.proxy_client.symfony.guzzle_client',
317
                $config['guzzle_client']
318
            );
319
        }
320 1
    }
321
322 15
    private function loadCacheTagging(ContainerBuilder $container, XmlFileLoader $loader, array $config, $client)
323
    {
324 15
        if ('auto' === $config['enabled'] && 'varnish' !== $client) {
325 2
            $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', false);
326
327 2
            return;
328
        }
329 13
        if ('varnish' !== $client) {
330 1
            throw new InvalidConfigurationException(sprintf('You can not enable cache tagging with %s', $client));
331
        }
332
333 12
        $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', true);
334 12
        $container->setParameter($this->getAlias().'.tag_handler.header', $config['header']);
335 12
        $loader->load('cache_tagging.xml');
336
337 12
        if (!empty($config['expression_language'])) {
338
            $container->setAlias(
339
                $this->getAlias().'.tag_handler.expression_language',
340
                $config['expression_language']
341
            );
342
        }
343
344 12
        if (!empty($config['rules'])) {
345 2
            $this->loadTagRules($container, $config['rules']);
346 2
        }
347
348 12
        $tagsHeader = $config['header'];
349 12
        $container->getDefinition($this->getAlias().'.cache_manager')
350 12
            ->addMethodCall('setTagsHeader', array($tagsHeader))
351
        ;
352 12
    }
353
354 1
    private function loadTest(ContainerBuilder $container, XmlFileLoader $loader, array $config)
355
    {
356 1
        $container->setParameter($this->getAlias().'.test.cache_header', $config['cache_header']);
357
358 1
        if ($config['proxy_server']) {
359 1
            $this->loadProxyServer($container, $loader, $config['proxy_server']);
360 1
        }
361
362 1
        if (isset($config['client']['varnish']['enabled'])
363 1
            || isset($config['client']['nginx']['enabled'])) {
364 1
            $loader->load('test_client.xml');
365
366 1
            if ($config['client']['varnish']['enabled']) {
367 1
                $loader->load('varnish_test_client.xml');
368 1
            }
369
370 1
            if ($config['client']['nginx']['enabled']) {
371
                $loader->load('nginx_test_client.xml');
372
            }
373
374 1
            $container->setAlias(
375 1
                $this->getAlias().'.test.default_client',
376 1
                $this->getAlias().'.test.client.'.$this->getDefaultProxyClient($config['client'])
377 1
            );
378 1
        }
379 1
    }
380
381 1
    private function loadProxyServer(ContainerBuilder $container, XmlFileLoader $loader, array $config)
382
    {
383 1
        if (isset($config['varnish'])) {
384 1
            $this->loadVarnishProxyServer($container, $loader, $config['varnish']);
385 1
        }
386
387 1
        if (isset($config['nginx'])) {
388
            $this->loadNginxProxyServer($container, $loader, $config['varnish']);
389
        }
390
391 1
        $container->setAlias(
392 1
            $this->getAlias().'.test.default_proxy_server',
393 1
            $this->getAlias().'.test.proxy_server.'.$this->getDefaultProxyClient($config)
394 1
        );
395 1
    }
396
397 1
    private function loadVarnishProxyServer(ContainerBuilder $container, XmlFileLoader $loader, $config)
398
    {
399 1
        $loader->load('varnish_proxy.xml');
400 1
        foreach ($config as $key => $value) {
401 1
            $container->setParameter(
402 1
                $this->getAlias().'.test.proxy_server.varnish.'.$key,
403
                $value
404 1
            );
405 1
        }
406 1
    }
407
408
    private function loadNginxProxyServer(ContainerBuilder $container, XmlFileLoader $loader, $config)
409
    {
410
        $loader->load('nginx_proxy.xml');
411
        foreach ($config as $key => $value) {
412
            $container->setParameter(
413
                $this->getAlias().'.test.proxy_server.nginx.'.$key,
414
                $value
415
            );
416
        }
417
    }
418
419 2
    private function loadTagRules(ContainerBuilder $container, array $config)
420
    {
421 2
        $tagDefinition = $container->getDefinition($this->getAlias().'.event_listener.tag');
422
423 2
        foreach ($config as $rule) {
424 2
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
425
426
            $tags = array(
427 2
                'tags' => $rule['tags'],
428 2
                'expressions' => $rule['tag_expressions'],
429 2
            );
430
431 2
            $tagDefinition->addMethodCall('addRule', array($ruleMatcher, $tags));
432 2
        }
433 2
    }
434
435 2
    private function loadInvalidatorRules(ContainerBuilder $container, array $config)
436
    {
437 2
        $tagDefinition = $container->getDefinition($this->getAlias().'.event_listener.invalidation');
438
439 2
        foreach ($config as $rule) {
440 2
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
441 2
            $tagDefinition->addMethodCall('addRule', array($ruleMatcher, $rule['routes']));
442 2
        }
443 2
    }
444
445 16
    private function validateUrl($url, $msg)
446
    {
447 16
        $prefixed = $this->prefixSchema($url);
448
449 16
        if (!$parts = parse_url($prefixed)) {
450 1
            throw new InvalidConfigurationException(sprintf($msg, $url));
451
        }
452 16
    }
453
454 16
    private function prefixSchema($url)
455
    {
456 16
        if (false === strpos($url, '://')) {
457 16
            $url = sprintf('%s://%s', 'http', $url);
458 16
        }
459
460 16
        return $url;
461
    }
462
463 15
    private function getDefaultProxyClient(array $config)
464
    {
465 15
        if (isset($config['default'])) {
466
            return $config['default'];
467
        }
468
469 15
        if (isset($config['varnish'])) {
470 12
            return 'varnish';
471
        }
472
473 3
        if (isset($config['nginx'])) {
474 2
            return 'nginx';
475
        }
476
477 1
        if (isset($config['symfony'])) {
478 1
            return 'symfony';
479
        }
480
481
        throw new InvalidConfigurationException('No proxy client configured');
482
    }
483
}
484