Completed
Push — 8.x-1.x ( fef2e1...0fa5ac )
by
unknown
25:33
created

HttpCacheEventSubscriber::setCacheProperties()   F

Complexity

Conditions 11
Paths 576

Size

Total Lines 49
Code Lines 26

Duplication

Lines 14
Ratio 28.57 %

Code Coverage

Tests 24
CRAP Score 11

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 14
loc 49
ccs 24
cts 24
cp 1
rs 3.2929
cc 11
eloc 26
nc 576
nop 3
crap 11

How to fix   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
namespace Drupal\controller_annotations\EventSubscriber;
4
5
use Drupal\controller_annotations\Configuration\Cache;
6
use Symfony\Component\HttpFoundation\Request;
7
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
8
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
9
use Symfony\Component\HttpKernel\KernelEvents;
10
use Symfony\Component\HttpFoundation\Response;
11
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
13
14
class HttpCacheEventSubscriber implements EventSubscriberInterface
15
{
16
17
    /**
18
     * @var \SplObjectStorage
19
     */
20
    private $lastModifiedDates;
21
22
    /**
23
     * @var \SplObjectStorage
24
     */
25
    private $eTags;
26
27
    /**
28
     * @var ExpressionLanguage
29
     */
30
    private $expressionLanguage;
31
32
    /**
33
     */
34 20
    public function __construct()
35
    {
36 20
        $this->lastModifiedDates = new \SplObjectStorage();
37 20
        $this->eTags = new \SplObjectStorage();
38 20
    }
39
40
    /**
41
     * Handles HTTP validation headers.
42
     *
43
     * @param FilterControllerEvent $event
44
     */
45 11
    public function onKernelController(FilterControllerEvent $event)
46
    {
47 11
        $request = $event->getRequest();
48 11
        if (!$configuration = $this->getConfiguration($request)) {
49 7
            return;
50
        }
51
52 4
        $response = new Response();
53
54 4
        if ($configuration->getLastModified()) {
55 2
            $this->setLastModified($request, $response, $configuration);
56
        }
57 4
        if ($configuration->getETag()) {
58 2
            $this->setETag($request, $response, $configuration);
59
        }
60 4
        if ($response->isNotModified($request)) {
61 2
            $event->setController(
62 2
              function () use ($response) {
63 2
                  return $response;
64 2
              }
65
            );
66 2
            $event->stopPropagation();
67
        }
68 4
    }
69
70
    /**
71
     * Modifies the response to apply HTTP cache headers when needed.
72
     *
73
     * @param FilterResponseEvent $event
74
     */
75 18
    public function onKernelResponse(FilterResponseEvent $event)
76
    {
77 18
        $request = $event->getRequest();
78 18
        if (!$configuration = $this->getConfiguration($request)) {
79 8
            return;
80
        }
81
82 10
        $response = $event->getResponse();
83 10
        if ($this->hasUncachableStatusCode($response)) {
84 1
            return;
85
        }
86
87 9
        $this->setCacheProperties($request, $response, $configuration);
88 9
    }
89
90
    /**
91
     * @param Request $request
92
     * @param Response $response
93
     * @param Cache $configuration
94
     */
95 2
    protected function setLastModified(
96
      Request $request,
97
      Response $response,
98
      Cache $configuration
99
    ) {
100 2
        $lastModifiedDate = $this->getExpressionLanguage()->evaluate(
101 2
          $configuration->getLastModified(),
102 2
          $request->attributes->all()
103
        );
104 2
        $response->setLastModified($lastModifiedDate);
0 ignored issues
show
Documentation introduced by
$lastModifiedDate is of type array, but the function expects a null|object<DateTime>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
105 2
        $this->lastModifiedDates[$request] = $lastModifiedDate;
106 2
    }
107
108
    /**
109
     * @param Request $request
110
     * @param Response $response
111
     * @param Cache $configuration
112
     */
113 2
    protected function setETag(
114
      Request $request,
115
      Response $response,
116
      Cache $configuration
117
    ) {
118 2
        $eTag = $this->createETag($request, $configuration);
119 2
        $response->setETag($eTag);
120 2
        $this->eTags[$request] = $eTag;
121 2
    }
122
123
    /**
124
     * @param Request $request
125
     * @param Cache $configuration
126
     *
127
     * @return string
128
     */
129 2
    protected function createETag(Request $request, Cache $configuration)
130
    {
131 2
        return hash(
132 2
          'sha256',
133 2
          $this->getExpressionLanguage()->evaluate(
134 2
            $configuration->getETag(),
135 2
            $request->attributes->all()
136
          )
137
        );
138
    }
139
140
    /**
141
     * @param $age
142
     *
143
     * @return float
144
     */
145 1
    protected function calculateAge($age)
146
    {
147 1
        $now = microtime(true);
148
149 1
        return ceil(strtotime($age, $now) - $now);
150
    }
151
152
    /**
153
     * @param Request $request
154
     *
155
     * @return Cache|false
156
     */
157 20
    protected function getConfiguration(Request $request)
158
    {
159 20
        $configuration = $request->attributes->get('_cache');
160 20
        if (empty($configuration) || !$configuration instanceof Cache) {
161 8
            return false;
162
        }
163
164 12
        return $configuration;
165
    }
166
167
    /**
168
     * @param Request $request
169
     * @param Response $response
170
     * @param Cache $configuration
171
     */
172 9
    protected function setCacheProperties(
173
      Request $request,
174
      Response $response,
175
      Cache $configuration
176
    ) {
177 9 View Code Duplication
        if (null !== $age = $configuration->getSMaxAge()) {
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...
178 2
            if (!is_numeric($age)) {
179 1
                $age = $this->calculateAge($configuration->getSMaxAge());
180
            }
181
182 2
            $response->setSharedMaxAge($age);
183
        }
184
185 9 View Code Duplication
        if (null !== $age = $configuration->getMaxAge()) {
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...
186 2
            if (!is_numeric($age)) {
187 1
                $age = $this->calculateAge($configuration->getMaxAge());
188
            }
189
190 2
            $response->setMaxAge($age);
191
        }
192
193 9
        if (null !== $configuration->getExpires()) {
194 1
            $response->setExpires($this->calculateExpires($configuration));
0 ignored issues
show
Security Bug introduced by
It seems like $this->calculateExpires($configuration) targeting Drupal\controller_annota...ber::calculateExpires() can also be of type false; however, Symfony\Component\HttpFo...\Response::setExpires() does only seem to accept null|object<DateTime>, did you maybe forget to handle an error condition?
Loading history...
195
        }
196
197 9
        if (null !== $configuration->getVary()) {
198 1
            $response->setVary($configuration->getVary());
199
        }
200
201 9
        if ($configuration->isPublic()) {
202 1
            $response->setPublic();
203
        }
204
205 9
        if ($configuration->isPrivate()) {
206 1
            $response->setPrivate();
207
        }
208
209 9
        if (isset($this->lastModifiedDates[$request])) {
210 1
            $response->setLastModified($this->lastModifiedDates[$request]);
211
212 1
            unset($this->lastModifiedDates[$request]);
213
        }
214
215 9
        if (isset($this->eTags[$request])) {
216 1
            $response->setETag($this->eTags[$request]);
217
218 1
            unset($this->eTags[$request]);
219
        }
220 9
    }
221
222
    /**
223
     * @param Cache $configuration
224
     *
225
     * @return bool|\DateTime
226
     */
227 1
    protected function calculateExpires(Cache $configuration)
228
    {
229 1
        return \DateTime::createFromFormat(
230 1
          'U',
231 1
          strtotime($configuration->getExpires()),
232 1
          new \DateTimeZone('UTC')
233
        );
234
    }
235
236
    /**
237
     * http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
238
     *
239
     * @param Response $response
240
     *
241
     * @return bool
242
     */
243 10
    protected function hasUncachableStatusCode(Response $response)
244
    {
245 10
        if (!in_array(
246 10
          $response->getStatusCode(),
247 10
          [200, 203, 300, 301, 302, 304, 404, 410]
248
        )) {
249 1
            return true;
250
        }
251
252 9
        return false;
253
    }
254
255
    /**
256
     * @codeCoverageIgnore
257
     * @return ExpressionLanguage
258
     */
259
    private function getExpressionLanguage()
260
    {
261
        if (null === $this->expressionLanguage) {
262
            if (!class_exists(ExpressionLanguage::class)) {
263
                throw new \RuntimeException(
264
                  'Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'
265
                );
266
            }
267
            $this->expressionLanguage = new ExpressionLanguage();
268
        }
269
270
        return $this->expressionLanguage;
271
    }
272
273
    /**
274
     * @return array
275
     */
276 7
    public static function getSubscribedEvents()
277
    {
278
        return [
279
          KernelEvents::CONTROLLER => [
280
            ['onKernelController', 0],
281 7
          ],
282
          KernelEvents::RESPONSE => [
283
            ['onKernelResponse', 100],
284
          ],
285
        ];
286
    }
287
}
288