Completed
Pull Request — master (#366)
by David
05:31
created

FOSHttpCacheExtension::loadCacheControl()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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