Completed
Push — master ( d2f8aa...3f97e5 )
by David de
66:53 queued 64:48
created

FOSHttpCacheExtension::loadTest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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