CacheControlListener::onKernelResponse()   D
last analyzed

Complexity

Conditions 18
Paths 164

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 18

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 53
ccs 30
cts 30
cp 1
rs 4.3333
cc 18
nc 164
nop 1
crap 18

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\EventListener;
13
14
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
15
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
19
use Symfony\Component\HttpKernel\Event\ResponseEvent;
20
use Symfony\Component\HttpKernel\Kernel;
21
use Symfony\Component\HttpKernel\KernelEvents;
22
23 1 View Code Duplication
if (Kernel::MAJOR_VERSION >= 5) {
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...
24 1
    class_alias(ResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\CacheControlResponseEvent');
25
} else {
26
    class_alias(FilterResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\CacheControlResponseEvent');
27
}
28
29
/**
30
 * Set caching settings on matching response according to the configurations.
31
 *
32
 * The first matching ruleset is applied.
33
 *
34
 * @author Lea Haensenberger <[email protected]>
35
 * @author David Buchmann <[email protected]>
36
 */
37
class CacheControlListener implements EventSubscriberInterface
38
{
39
    /**
40
     * Whether to skip this response and not set any cache headers.
41
     *
42
     * @var bool
43
     */
44
    private $skip = false;
45
46
    /**
47
     * Cache control directives directly supported by Response.
48
     *
49
     * @var array
50
     */
51
    private $supportedDirectives = [
52
        'max_age' => true,
53
        's_maxage' => true,
54
        'private' => true,
55
        'public' => true,
56
    ];
57
58
    /**
59
     * @var array List of arrays with RuleMatcherInterface, settings array
60
     */
61
    private $rulesMap = [];
62
63
    /**
64
     * If not empty, add a debug header with that name to all responses,
65
     * telling the cache proxy to add debug output.
66
     *
67
     * @var string|bool Name of the header or false to add no header
68
     */
69
    private $debugHeader;
70
71
    /**
72
     * @param string|bool $debugHeader Header to set to trigger debugging, or false to send no header
73
     */
74 37
    public function __construct($debugHeader = false)
75
    {
76 37
        $this->debugHeader = $debugHeader;
77 37
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 2
    public static function getSubscribedEvents()
83
    {
84
        return [
85 2
            KernelEvents::RESPONSE => ['onKernelResponse', 10],
86
        ];
87
    }
88
89
    /**
90
     * Set whether to skip this response completely.
91
     *
92
     * This can be called when other parts of the application took care of all
93
     * cache headers. No attempt to merge cache headers is made anymore.
94
     *
95
     * The debug header is still added if configured.
96
     *
97
     * @param bool $skip
98
     */
99 1
    public function setSkip($skip = true)
100
    {
101 1
        $this->skip = $skip;
102 1
    }
103
104
    /**
105
     * Apply the header rules if the request matches.
106
     */
107 36
    public function onKernelResponse(CacheControlResponseEvent $event)
108
    {
109 36
        $request = $event->getRequest();
110 36
        $response = $event->getResponse();
111
112 36
        if ($this->debugHeader) {
113 20
            $response->headers->set($this->debugHeader, 1, false);
114
        }
115
116
        // do not change cache directives on non-cacheable requests.
117 36
        if ($this->skip || !$request->isMethodCacheable()) {
118 10
            return;
119
        }
120
121 26
        $options = $this->matchRule($request, $response);
122
123 26
        if (false === $options) {
124 11
            return;
125
        }
126
127 16
        if (!empty($options['cache_control'])) {
128 12
            $directives = array_intersect_key($options['cache_control'], $this->supportedDirectives);
129 12
            $extraDirectives = array_diff_key($options['cache_control'], $directives);
130 12
            if (!empty($directives)) {
131 9
                $this->setCache($response, $directives, $options['overwrite']);
132
            }
133 12
            if (!empty($extraDirectives)) {
134 7
                $this->setExtraCacheDirectives($response, $extraDirectives, $options['overwrite']);
135
            }
136
        }
137
138 16
        if (isset($options['reverse_proxy_ttl'])
139 16
            && null !== $options['reverse_proxy_ttl']
140 16
            && !$response->headers->has('X-Reverse-Proxy-TTL')
141
        ) {
142 1
            $response->headers->set('X-Reverse-Proxy-TTL', (int) $options['reverse_proxy_ttl'], false);
143
        }
144
145 16
        if (!empty($options['vary'])) {
146 3
            $response->setVary($options['vary'], $options['overwrite']);
147
        }
148
149 16
        if (!empty($options['etag'])
150 16
            && ($options['overwrite'] || null === $response->getEtag())
151
        ) {
152 3
            $response->setEtag(md5($response->getContent()), 'weak' === $options['etag']);
153
        }
154 16
        if (isset($options['last_modified'])
155 16
            && ($options['overwrite'] || null === $response->getLastModified())
156
        ) {
157 3
            $response->setLastModified(new \DateTime($options['last_modified']));
158
        }
159 16
    }
160
161
    /**
162
     * Add a rule matcher with a list of header directives to apply if the
163
     * request and response are matched.
164
     *
165
     * @param RuleMatcherInterface $ruleMatcher The headers apply to request and response matched by this matcher
166
     * @param array                $settings    An array of header configuration
167
     */
168 37
    public function addRule(
169
        RuleMatcherInterface $ruleMatcher,
170
        array $settings = []
171
    ) {
172 37
        $this->rulesMap[] = [$ruleMatcher, $settings];
173 37
    }
174
175
    /**
176
     * Return the settings for the current request if any rule matches.
177
     *
178
     * @return array|false Settings to apply or false if no rule matched
179
     */
180 View Code Duplication
    private function matchRule(Request $request, Response $response)
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...
181
    {
182 26
        foreach ($this->rulesMap as $elements) {
183
            if ($elements[0]->matches($request, $response)) {
184 26
                return $elements[1];
185 26
            }
186 16
        }
187
188
        return false;
189
    }
190 11
191
    /**
192
     * Set cache headers on response.
193
     *
194
     * @param bool $overwrite Whether to keep existing cache headers or to overwrite them
195
     */
196
    private function setCache(Response $response, array $directives, $overwrite)
197
    {
198
        if ($overwrite) {
199
            $response->setCache($directives);
200 9
201
            return;
202 9
        }
203 1
204
        if (false !== strpos($response->headers->get('Cache-Control'), 'no-cache')) {
205 1
            // this single header is set by default. if its the only thing, we override it.
206
            $response->setCache($directives);
207
208 8
            return;
209
        }
210 6
211
        foreach (array_keys($this->supportedDirectives) as $key) {
212 6
            $directive = str_replace('_', '-', $key);
213
            if ($response->headers->hasCacheControlDirective($directive)) {
214
                $directives[$key] = $response->headers->getCacheControlDirective($directive);
215 2
            }
216 2
            if ('public' === $directive && $response->headers->hasCacheControlDirective('private')
217 2
                || 'private' === $directive && $response->headers->hasCacheControlDirective('public')
218 2
            ) {
219
                unset($directives[$key]);
220 2
            }
221 2
        }
222
223 2
        $response->setCache($directives);
224
    }
225
226
    /**
227 2
     * Add extra cache control directives on response.
228 2
     *
229
     * @param bool $overwrite Whether to keep existing cache headers or to overwrite them
230
     */
231
    private function setExtraCacheDirectives(Response $response, array $controls, $overwrite)
232
    {
233
        $flags = ['must_revalidate', 'proxy_revalidate', 'no_transform', 'no_cache', 'no_store'];
234
        $options = ['stale_if_error', 'stale_while_revalidate'];
235
236 View Code Duplication
        foreach ($flags as $key) {
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 7
            $flag = str_replace('_', '-', $key);
238
            if (!empty($controls[$key])
239 7
                && ($overwrite || !$response->headers->hasCacheControlDirective($flag))
240 7
            ) {
241
                $response->headers->addCacheControlDirective($flag);
242 7
            }
243 7
        }
244 7
245 7 View Code Duplication
        foreach ($options as $key) {
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...
246
            $option = str_replace('_', '-', $key);
247 6
            if (isset($controls[$key])
248
                && ($overwrite || !$response->headers->hasCacheControlDirective($option))
249
            ) {
250
                $response->headers->addCacheControlDirective($option, $controls[$key]);
251 7
            }
252 7
        }
253 7
    }
254
}
255