Passed
Pull Request — master (#19)
by Jitendra
01:47
created

Cache::after()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 2
1
<?php
2
3
namespace PhalconExt\Http\Middleware;
4
5
use Phalcon\Http\Request;
6
use Phalcon\Http\Response;
7
use PhalconExt\Http\BaseMiddleware;
8
9
/**
10
 * Cache middleware that caches request output for fast performance.
11
 *
12
 * @author  Jitendra Adhikari <[email protected]>
13
 * @license MIT
14
 *
15
 * @link    https://github.com/adhocore/phalcon-ext
16
 */
17
class Cache extends BaseMiddleware
18
{
19
    /** @var string */
20
    protected $cacheKey  = '';
21
22
    protected $configKey = 'httpCache';
23
24
    protected $willCache = false;
25
26
    /**
27
     * Get the recent cache key used.
28
     *
29
     * @return string
30
     */
31
    public function getLastKey(): string
32
    {
33
        return $this->cacheKey;
34
    }
35
36
    /**
37
     * Handle the cache.
38
     *
39
     * @param Request  $request
40
     * @param Response $response
41
     *
42
     * @return bool
43
     */
44
    public function before(Request $request, Response $response): bool
45
    {
46
        if (!$this->isCacheable($request, $response)) {
47
            return true;
48
        }
49
50
        $this->cacheKey = $this->getCacheKey($request);
51
52
        if (!$this->hasCache($this->cacheKey)) {
53
            return $this->willCache = true;
54
        }
55
56
        return $this->serve($response);
57
    }
58
59
    /**
60
     * Check if the output for current request is cachaeble.
61
     *
62
     * @param Request  $request
63
     * @param Response $response
64
     *
65
     * @return bool
66
     */
67
    protected function isCacheable(Request $request, Response $response): bool
68
    {
69
        if (!$request->isGet()) {
70
            return false;
71
        }
72
73
        list($routeName, $url) = $this->getRouteNameUri();
74
75
        $allowedRoutes = \array_fill_keys($this->config['routes'], true);
76
77
        if (!isset($allowedRoutes[$routeName]) && !isset($allowedRoutes[$url])) {
78
            return false;
79
        }
80
81
        $statusCode = $response->getStatusCode();
82
83
        return \in_array($statusCode, [200, 204, 301, null]); // null doesnt indicate failure!
84
    }
85
86
    /**
87
     * Checks if there is cache for key corresponding to current request.
88
     *
89
     * @param string $cacheKey
90
     *
91
     * @return bool
92
     */
93
    protected function hasCache(string $cacheKey): bool
94
    {
95
        return $this->di('redis')->exists($cacheKey);
96
    }
97
98
    /**
99
     * Get cacheKey for current request.
100
     *
101
     * @param Request $request
102
     *
103
     * @return string
104
     */
105
    protected function getCacheKey(Request $request): string
106
    {
107
        if ($this->cacheKey) {
108
            return $this->cacheKey;
109
        }
110
111
        $query = $request->getQuery();
112
        \sort($query);
113
114
        return $this->cacheKey = \md5($request->getUri() . '?' . \http_build_query($query));
115
    }
116
117
    /**
118
     * Output the cached response with correct header.
119
     *
120
     * @param Response $response
121
     *
122
     * @return bool
123
     */
124
    protected function serve(Response $response): bool
125
    {
126
        $cached = \json_decode($this->di('redis')->get($this->cacheKey));
127
128
        foreach ($cached->headers as $name => $value) {
129
            $response->setHeader($name, $value);
130
        }
131
132
        $response->setContent($cached->content)->send();
133
134
        return false;
135
    }
136
137
    /**
138
     * Write the just sent response to cache.
139
     *
140
     * @param Request  $request
141
     * @param Response $response
142
     *
143
     * @return bool
144
     */
145
    public function after(Request $request, Response $response): bool
146
    {
147
        if (!$this->willCache) {
148
            return true;
149
        }
150
151
        $headers  = ['X-Cache' => \time(), 'X-Cache-ID' => $this->cacheKey];
152
153
        foreach ($response->getHeaders()->toArray() as $key => $value) {
154
            if (\strpos($key, 'Access-Control-') === false) {
155
                $headers[$key] = $value;
156
            }
157
        }
158
159
        $this->di('redis')->save($this->cacheKey, \json_encode([
160
            'headers' => $headers,
161
            'content' => $this->getContent($response),
162
        ]), $this->config['ttl'] * 60);
163
164
        return true;
165
    }
166
167
    /**
168
     * Get the content string.
169
     *
170
     * @param Response $response
171
     *
172
     * @return string
173
     */
174
    protected function getContent(Response $response): string
175
    {
176
        if (null !== $response->getContent()) {
177
            return $response->getContent();
178
        }
179
180
        if ($this->isMicro()) {
181
            return (string) $this->di('application')->getReturnedValue();
182
        }
183
184
        $value = $this->di('dispatcher')->getReturnedValue();
185
186
        if (\method_exists($value, 'getContent')) {
187
            return $value->getContent();
188
        }
189
190
        return (string) $value;
191
    }
192
}
193