Completed
Push — master ( ea4d71...e95b62 )
by David
9s
created

FOSHttpCacheExtension::loadCacheTagging()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.8984

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 10
cts 16
cp 0.625
rs 8.439
c 0
b 0
f 0
cc 6
eloc 15
nc 6
nop 4
crap 7.8984
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\HttpDispatcher;
15
use FOS\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass;
16
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\Definition;
20
use Symfony\Component\DependencyInjection\DefinitionDecorator;
21
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
26
/**
27
 * {@inheritdoc}
28
 */
29
class FOSHttpCacheExtension extends Extension
30
{
31
    /**
32
     * {@inheritdoc}
33
     */
34 1
    public function getConfiguration(array $config, ContainerBuilder $container)
35
    {
36 1
        return new Configuration($container->getParameter('kernel.debug'));
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 1
    public function load(array $configs, ContainerBuilder $container)
43
    {
44 1
        $configuration = $this->getConfiguration($configs, $container);
45 1
        $config = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 44 can be null; however, Symfony\Component\Depend...:processConfiguration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
46
47 1
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
48 1
        $loader->load('matcher.xml');
49
50 1
        if ($config['debug']['enabled'] || (!empty($config['cache_control']))) {
51 1
            $debugHeader = $config['debug']['enabled'] ? $config['debug']['header'] : false;
52 1
            $container->setParameter($this->getAlias().'.debug_header', $debugHeader);
53 1
            $loader->load('cache_control_listener.xml');
54
        }
55
56 1
        if (!empty($config['cache_control'])) {
57 1
            $this->loadCacheControl($container, $config['cache_control']);
58
        }
59
60 1
        if (isset($config['proxy_client'])) {
61 1
            $this->loadProxyClient($container, $loader, $config['proxy_client']);
62
        }
63
64 1
        if (isset($config['test'])) {
65 1
            $this->loadTest($container, $loader, $config['test']);
66
        }
67
68 1
        if ($config['cache_manager']['enabled']) {
69 1
            if (array_key_exists('custom_proxy_client', $config['cache_manager'])) {
70
                // overwrite the previously set alias, if a proxy client was also configured
71
                $container->setAlias(
72
                    $this->getAlias().'.default_proxy_client',
73
                    $config['cache_manager']['custom_proxy_client']
74
                );
75
            }
76 1
            if ('auto' === $config['cache_manager']['generate_url_type']) {
77 1
                if (array_key_exists('custom_proxy_client', $config['cache_manager'])) {
78
                    $generateUrlType = UrlGeneratorInterface::ABSOLUTE_URL;
79
                } else {
80 1
                    $defaultClient = $this->getDefaultProxyClient($config['proxy_client']);
81 1
                    $generateUrlType = array_key_exists('base_url', $config['proxy_client'][$defaultClient])
82
                        ? UrlGeneratorInterface::ABSOLUTE_PATH
83 1
                        : UrlGeneratorInterface::ABSOLUTE_URL
84
                    ;
85
                }
86
            } else {
87
                $generateUrlType = $config['cache_manager']['generate_url_type'];
88
            }
89 1
            $container->setParameter($this->getAlias().'.cache_manager.generate_url_type', $generateUrlType);
90 1
            $loader->load('cache_manager.xml');
91
        }
92
93 1
        if ($config['tags']['enabled']) {
94 1
            $this->loadCacheTagging(
95
                $container,
96
                $loader,
97 1
                $config['tags'],
98 1
                array_key_exists('proxy_client', $config)
99 1
                    ? $this->getDefaultProxyClient($config['proxy_client'])
100 1
                    : 'custom'
101
            );
102
        } else {
103
            $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', false);
104
        }
105
106 1
        if ($config['invalidation']['enabled']) {
107 1
            $loader->load('invalidation_listener.xml');
108
109 1
            if (!empty($config['invalidation']['expression_language'])) {
110
                $container->setAlias(
111
                    $this->getAlias().'.invalidation.expression_language',
112
                    $config['invalidation']['expression_language']
113
                );
114
            }
115
116 1
            if (!empty($config['invalidation']['rules'])) {
117 1
                $this->loadInvalidatorRules($container, $config['invalidation']['rules']);
118
            }
119
        }
120
121 1
        if ($config['user_context']['enabled']) {
122 1
            $this->loadUserContext($container, $loader, $config['user_context']);
123
        }
124
125 1
        if (!empty($config['flash_message']) && $config['flash_message']['enabled']) {
126
            $container->setParameter($this->getAlias().'.event_listener.flash_message.options', $config['flash_message']);
127
128
            $loader->load('flash_message.xml');
129
        }
130 1
    }
131
132
    /**
133
     * @param ContainerBuilder $container
134
     * @param array            $config
135
     *
136
     * @throws InvalidConfigurationException
137
     */
138 1
    private function loadCacheControl(ContainerBuilder $container, array $config)
139
    {
140 1
        $controlDefinition = $container->getDefinition($this->getAlias().'.event_listener.cache_control');
141
142 1
        foreach ($config['rules'] as $rule) {
143 1
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
144
145 1
            if ('default' === $rule['headers']['overwrite']) {
146 1
                $rule['headers']['overwrite'] = $config['defaults']['overwrite'];
147
            }
148
149 1
            $controlDefinition->addMethodCall('addRule', array($ruleMatcher, $rule['headers']));
150
        }
151 1
    }
152
153 1
    private function parseRuleMatcher(ContainerBuilder $container, array $match)
154
    {
155 1
        $match['ips'] = (empty($match['ips'])) ? null : $match['ips'];
156
157 1
        $requestMatcher = $this->createRequestMatcher(
158
            $container,
159 1
            $match['path'],
160 1
            $match['host'],
161 1
            $match['methods'],
162 1
            $match['ips'],
163 1
            $match['attributes']
164
        );
165
166 1
        $extraCriteria = array();
167 1
        foreach (array('additional_cacheable_status', 'match_response') as $extra) {
168 1
            if (isset($match[$extra])) {
169 1
                $extraCriteria[$extra] = $match[$extra];
170
            }
171
        }
172
173 1
        return $this->createRuleMatcher(
174
            $container,
175
            $requestMatcher,
176
            $extraCriteria
177
        );
178
    }
179
180 1
    private function createRuleMatcher(ContainerBuilder $container, Reference $requestMatcher, array $extraCriteria)
181
    {
182 1
        $arguments = array((string) $requestMatcher, $extraCriteria);
183 1
        $serialized = serialize($arguments);
184 1
        $id = $this->getAlias().'.rule_matcher.'.md5($serialized).sha1($serialized);
185
186 1 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...
187
            $container
188 1
                ->setDefinition($id, new DefinitionDecorator($this->getAlias().'.rule_matcher'))
189 1
                ->replaceArgument(0, $requestMatcher)
190 1
                ->replaceArgument(1, $extraCriteria)
191
            ;
192
        }
193
194 1
        return new Reference($id);
195
    }
196
197 1
    private function loadUserContext(ContainerBuilder $container, XmlFileLoader $loader, array $config)
198
    {
199 1
        $loader->load('user_context.xml');
200
201 1
        $container->getDefinition($this->getAlias().'.user_context.request_matcher')
202 1
            ->replaceArgument(0, $config['match']['accept'])
203 1
            ->replaceArgument(1, $config['match']['method']);
204
205 1
        $container->getDefinition($this->getAlias().'.event_listener.user_context')
206 1
            ->replaceArgument(0, new Reference($config['match']['matcher_service']))
207 1
            ->replaceArgument(2, $config['user_identifier_headers'])
208 1
            ->replaceArgument(3, $config['user_hash_header'])
209 1
            ->replaceArgument(4, $config['hash_cache_ttl'])
210 1
            ->replaceArgument(5, $config['always_vary_on_context_hash']);
211
212 1
        $container->getDefinition($this->getAlias().'.user_context.anonymous_request_matcher')
213 1
            ->replaceArgument(0, $config['user_identifier_headers']);
214
215 1
        if ($config['logout_handler']['enabled']) {
216 1
            $container->getDefinition($this->getAlias().'.user_context.logout_handler')
217 1
                ->replaceArgument(1, $config['user_identifier_headers'])
218 1
                ->replaceArgument(2, $config['match']['accept']);
219
        } else {
220
            $container->removeDefinition($this->getAlias().'.user_context.logout_handler');
221
        }
222
223 1
        if ($config['role_provider']) {
224 1
            $container->getDefinition($this->getAlias().'.user_context.role_provider')
225 1
                ->addTag(HashGeneratorPass::TAG_NAME)
226 1
                ->setAbstract(false);
227
        }
228 1
    }
229
230 1
    private function createRequestMatcher(ContainerBuilder $container, $path = null, $host = null, $methods = null, $ips = null, array $attributes = array())
231
    {
232 1
        $arguments = array($path, $host, $methods, $ips, $attributes);
233 1
        $serialized = serialize($arguments);
234 1
        $id = $this->getAlias().'.request_matcher.'.md5($serialized).sha1($serialized);
235
236 1 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...
237
            $container
238 1
                ->setDefinition($id, new DefinitionDecorator($this->getAlias().'.request_matcher'))
239 1
                ->setArguments($arguments)
240
            ;
241
        }
242
243 1
        return new Reference($id);
244
    }
245
246 1
    private function loadProxyClient(ContainerBuilder $container, XmlFileLoader $loader, array $config)
247
    {
248 1
        if (isset($config['varnish'])) {
249 1
            $this->loadVarnish($container, $loader, $config['varnish']);
250
        }
251 1
        if (isset($config['nginx'])) {
252
            $this->loadNginx($container, $loader, $config['nginx']);
253
        }
254 1
        if (isset($config['symfony'])) {
255
            $this->loadSymfony($container, $loader, $config['symfony']);
256
        }
257
258 1
        $container->setAlias(
259 1
            $this->getAlias().'.default_proxy_client',
260 1
            $this->getAlias().'.proxy_client.'.$this->getDefaultProxyClient($config)
261
        );
262 1
    }
263
264
    /**
265
     * Define the http dispatcher service for the proxy client $name.
266
     *
267
     * @param ContainerBuilder $container
268
     * @param array            $config
269
     * @param string           $serviceName
270
     */
271 1
    private function createHttpDispatcherDefinition(ContainerBuilder $container, array $config, $serviceName)
272
    {
273 1
        foreach ($config['servers'] as $url) {
274 1
            $this->validateUrl($url, 'Not a valid Varnish server address: "%s"');
275
        }
276 1
        if (!empty($config['base_url'])) {
277 1
            $baseUrl = $this->prefixSchema($config['base_url']);
278 1
            $this->validateUrl($baseUrl, 'Not a valid base path: "%s"');
279
        } else {
280
            $baseUrl = null;
281
        }
282 1
        $httpClient = null;
283 1
        if ($config['http_client']) {
284
            $httpClient = new Reference($config['http_client']);
285
        }
286
287 1
        $definition = new Definition(HttpDispatcher::class, [
288 1
            $config['servers'],
289 1
            $baseUrl,
290 1
            $httpClient,
291
        ]);
292
293 1
        $container->setDefinition($serviceName, $definition);
294 1
    }
295
296 1
    private function loadVarnish(ContainerBuilder $container, XmlFileLoader $loader, array $config)
297
    {
298 1
        $this->createHttpDispatcherDefinition($container, $config['http'], $this->getAlias().'.proxy_client.varnish.http_dispatcher');
299 1
        $loader->load('varnish.xml');
300 1
    }
301
302
    private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, array $config)
303
    {
304
        $this->createHttpDispatcherDefinition($container, $config['http'], $this->getAlias().'.proxy_client.nginx.http_dispatcher');
305
        $container->setParameter($this->getAlias().'.proxy_client.nginx.options', [
306
            'purge_location' => $config['purge_location'],
307
        ]);
308
        $loader->load('nginx.xml');
309
    }
310
311
    private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, array $config)
312
    {
313
        $this->createHttpDispatcherDefinition($container, $config['http'], $this->getAlias().'.proxy_client.symfony.http_dispatcher');
314
        $loader->load('symfony.xml');
315
    }
316
317
    /**
318
     * @param ContainerBuilder $container
319
     * @param XmlFileLoader    $loader
320
     * @param array            $config    Configuration section for the tags node
321
     * @param string           $client    Name of the client used with the cache manager,
322
     *                                    "custom" when a custom client is used
323
     */
324 1
    private function loadCacheTagging(ContainerBuilder $container, XmlFileLoader $loader, array $config, $client)
325
    {
326 1
        if ('auto' === $config['enabled'] && 'varnish' !== $client) {
327
            $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', false);
328
329
            return;
330
        }
331 1
        if (!in_array($client, array('varnish', 'custom'))) {
332
            throw new InvalidConfigurationException(sprintf('You can not enable cache tagging with the %s client', $client));
333
        }
334
335 1
        $container->setParameter($this->getAlias().'.compiler_pass.tag_annotations', true);
336 1
        $container->setParameter($this->getAlias().'.tag_handler.header', $config['header']);
337 1
        $loader->load('cache_tagging.xml');
338
339 1
        if (!empty($config['expression_language'])) {
340
            $container->setAlias(
341
                $this->getAlias().'.tag_handler.expression_language',
342
                $config['expression_language']
343
            );
344
        }
345
346 1
        if (!empty($config['rules'])) {
347 1
            $this->loadTagRules($container, $config['rules']);
348
        }
349 1
    }
350
351 1
    private function loadTest(ContainerBuilder $container, XmlFileLoader $loader, array $config)
352
    {
353 1
        $container->setParameter($this->getAlias().'.test.cache_header', $config['cache_header']);
354
355 1
        if ($config['proxy_server']) {
356 1
            $this->loadProxyServer($container, $loader, $config['proxy_server']);
357
        }
358 1
    }
359
360 1
    private function loadProxyServer(ContainerBuilder $container, XmlFileLoader $loader, array $config)
361
    {
362 1
        if (isset($config['varnish'])) {
363 1
            $this->loadVarnishProxyServer($container, $loader, $config['varnish']);
364
        }
365
366 1
        if (isset($config['nginx'])) {
367
            $this->loadNginxProxyServer($container, $loader, $config['varnish']);
368
        }
369
370 1
        $container->setAlias(
371 1
            $this->getAlias().'.test.default_proxy_server',
372 1
            $this->getAlias().'.test.proxy_server.'.$this->getDefaultProxyClient($config)
373
        );
374 1
    }
375
376 1
    private function loadVarnishProxyServer(ContainerBuilder $container, XmlFileLoader $loader, $config)
377
    {
378 1
        $loader->load('varnish_proxy.xml');
379 1
        foreach ($config as $key => $value) {
380 1
            $container->setParameter(
381 1
                $this->getAlias().'.test.proxy_server.varnish.'.$key,
382
                $value
383
            );
384
        }
385 1
    }
386
387
    private function loadNginxProxyServer(ContainerBuilder $container, XmlFileLoader $loader, $config)
388
    {
389
        $loader->load('nginx_proxy.xml');
390
        foreach ($config as $key => $value) {
391
            $container->setParameter(
392
                $this->getAlias().'.test.proxy_server.nginx.'.$key,
393
                $value
394
            );
395
        }
396
    }
397
398 1
    private function loadTagRules(ContainerBuilder $container, array $config)
399
    {
400 1
        $tagDefinition = $container->getDefinition($this->getAlias().'.event_listener.tag');
401
402 1
        foreach ($config as $rule) {
403 1
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
404
405
            $tags = array(
406 1
                'tags' => $rule['tags'],
407 1
                'expressions' => $rule['tag_expressions'],
408
            );
409
410 1
            $tagDefinition->addMethodCall('addRule', array($ruleMatcher, $tags));
411
        }
412 1
    }
413
414 1
    private function loadInvalidatorRules(ContainerBuilder $container, array $config)
415
    {
416 1
        $tagDefinition = $container->getDefinition($this->getAlias().'.event_listener.invalidation');
417
418 1
        foreach ($config as $rule) {
419 1
            $ruleMatcher = $this->parseRuleMatcher($container, $rule['match']);
420 1
            $tagDefinition->addMethodCall('addRule', array($ruleMatcher, $rule['routes']));
421
        }
422 1
    }
423
424 1
    private function validateUrl($url, $msg)
425
    {
426 1
        $prefixed = $this->prefixSchema($url);
427
428 1
        if (!$parts = parse_url($prefixed)) {
429
            throw new InvalidConfigurationException(sprintf($msg, $url));
430
        }
431 1
    }
432
433 1
    private function prefixSchema($url)
434
    {
435 1
        if (false === strpos($url, '://')) {
436 1
            $url = sprintf('%s://%s', 'http', $url);
437
        }
438
439 1
        return $url;
440
    }
441
442 1
    private function getDefaultProxyClient(array $config)
443
    {
444 1
        if (isset($config['default'])) {
445
            return $config['default'];
446
        }
447
448 1
        if (isset($config['varnish'])) {
449 1
            return 'varnish';
450
        }
451
452
        if (isset($config['nginx'])) {
453
            return 'nginx';
454
        }
455
456
        if (isset($config['symfony'])) {
457
            return 'symfony';
458
        }
459
460
        throw new InvalidConfigurationException('No proxy client configured');
461
    }
462
}
463