Completed
Push — master ( 1c49e0...cbbbc4 )
by David
04:53
created

FOSHttpCacheExtension::parseRuleMatcher()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

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