Passed
Push — master ( a6375b...78beea )
by Anton
05:54
created

CacheControl::setLastModified()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link      https://github.com/bluzphp/framework
7
 */
8
9
declare(strict_types=1);
10
11
namespace Bluz\Http;
12
13
use Bluz\Common\Container\Container;
14
use Bluz\Response\Response;
15
16
/**
17
 * HTTP Cache Control
18
 *
19
 * Wrapper for working with HTTP headers
20
 *     - Cache-Control
21
 *     - Last-Modified
22
 *     - Expires
23
 *     - ETag
24
 *     - Age
25
 *
26
 * @package  Bluz\Http
27
 * @author   Anton Shevchuk
28
 * @link     http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
29
 * @link     http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
30
 */
31
class CacheControl
32
{
33
    use Container;
34
35
    /**
36
     * @var Response instance
37
     */
38
    protected $response;
39
40
    /**
41
     * Create instance
42
     *
43
     * @param Response $response
44
     */
45 11
    public function __construct($response)
46
    {
47 11
        $this->response = $response;
48 11
    }
49
50
    /**
51
     * Prepare Cache-Control header
52
     *
53
     * @return void
54
     */
55 7
    protected function updateCacheControlHeader(): void
56
    {
57 7
        $parts = [];
58 7
        ksort($this->container);
59 7
        foreach ($this->container as $key => $value) {
60 7
            if (true === $value) {
61 4
                $parts[] = $key;
62
            } else {
63 5
                if (preg_match('#[^a-zA-Z0-9._-]#', (string)$value)) {
64
                    $value = '"' . $value . '"';
65
                }
66 5
                $parts[] = "$key=$value";
67
            }
68
        }
69 7
        if (\count($parts)) {
70 7
            $this->response->setHeader('Cache-Control', implode(', ', $parts));
71
        }
72 7
    }
73
74
    /**
75
     * Marks the response as "private".
76
     *
77
     * It makes the response ineligible for serving other clients.
78
     *
79
     * @return void
80
     */
81 1
    public function setPrivate(): void
82
    {
83 1
        $this->doDeleteContainer('public');
84 1
        $this->doSetContainer('private', true);
85 1
        $this->updateCacheControlHeader();
86 1
    }
87
88
    /**
89
     * Marks the response as "public".
90
     *
91
     * It makes the response eligible for serving other clients.
92
     *
93
     * @return void
94
     */
95 3
    public function setPublic(): void
96
    {
97 3
        $this->doDeleteContainer('private');
98 3
        $this->doSetContainer('public', true);
99 3
        $this->updateCacheControlHeader();
100 3
    }
101
102
    /**
103
     * Returns the number of seconds after the time specified in the response's Date
104
     * header when the response should no longer be considered fresh.
105
     *
106
     * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
107
     * back on an expires header. It returns null when no maximum age can be established.
108
     *
109
     * @return integer|null Number of seconds
110
     */
111 6
    public function getMaxAge(): ?int
112
    {
113 6
        if ($this->doContainsContainer('s-maxage')) {
114 1
            return (int)$this->doGetContainer('s-maxage');
115
        }
116
117 5
        if ($this->doContainsContainer('max-age')) {
118 2
            return (int)$this->doGetContainer('max-age');
119
        }
120
121 3
        if ($expires = $this->getExpires()) {
122 1
            $expires = \DateTime::createFromFormat(DATE_RFC2822, $expires);
123 1
            return (int) $expires->format('U') - date('U');
124
        }
125
126 2
        return null;
127
    }
128
129
    /**
130
     * Sets the number of seconds after which the response should no longer be considered fresh.
131
     *
132
     * This methods sets the Cache-Control max-age directive.
133
     *
134
     * @param  integer $value Number of seconds
135
     *
136
     * @return void
137
     */
138 3
    public function setMaxAge($value): void
139
    {
140 3
        $this->doSetContainer('max-age', $value);
141 3
        $this->updateCacheControlHeader();
142 3
    }
143
144
    /**
145
     * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
146
     *
147
     * This methods sets the Cache-Control s-maxage directive.
148
     *
149
     * @param  integer $value Number of seconds
150
     *
151
     * @return void
152
     */
153 2
    public function setSharedMaxAge($value): void
154
    {
155 2
        $this->setPublic();
156 2
        $this->doSetContainer('s-maxage', $value);
157 2
        $this->updateCacheControlHeader();
158 2
    }
159
160
    /**
161
     * Returns the response's time-to-live in seconds.
162
     *
163
     * It returns null when no freshness information is present in the response.
164
     * When the responses TTL is <= 0, the response may not be served from cache without first
165
     * revalidating with the origin.
166
     *
167
     * @return integer|null The TTL in seconds
168
     */
169 2
    public function getTtl(): ?int
170
    {
171 2
        if ($maxAge = $this->getMaxAge()) {
172 1
            return $maxAge - $this->getAge();
173
        }
174 1
        return null;
175
    }
176
177
    /**
178
     * Sets the response's time-to-live for shared caches.
179
     *
180
     * This method adjusts the Cache-Control/s-maxage directive.
181
     *
182
     * @param  integer $seconds Number of seconds
183
     *
184
     * @return void
185
     */
186 1
    public function setTtl($seconds): void
187
    {
188 1
        $this->setSharedMaxAge($this->getAge() + $seconds);
189 1
    }
190
191
    /**
192
     * Sets the response's time-to-live for private/client caches.
193
     *
194
     * This method adjusts the Cache-Control/max-age directive.
195
     *
196
     * @param  integer $seconds Number of seconds
197
     *
198
     * @return void
199
     */
200 1
    public function setClientTtl($seconds): void
201
    {
202 1
        $this->setMaxAge($this->getAge() + $seconds);
203 1
    }
204
205
    /**
206
     * Returns the literal value of the ETag HTTP header
207
     *
208
     * @return string The ETag HTTP header or null if it does not exist
209
     */
210 1
    public function getEtag(): string
211
    {
212 1
        return $this->response->getHeader('ETag');
213
    }
214
215
    /**
216
     * Sets the ETag value
217
     *
218
     * @param  string $etag The ETag unique identifier
219
     * @param  bool   $weak Whether you want a weak ETag or not
220
     *
221
     * @return void
222
     */
223 1
    public function setEtag($etag, $weak = false): void
224
    {
225 1
        $etag = trim($etag, '"');
226 1
        $this->response->setHeader('ETag', (true === $weak ? 'W/' : '') . '"' . $etag . '"');
227 1
    }
228
229
    /**
230
     * Returns the age of the response
231
     *
232
     * @return integer The age of the response in seconds
233
     */
234 3
    public function getAge(): int
235
    {
236 3
        if ($age = $this->response->getHeader('Age')) {
237
            return (int)$age;
238
        }
239 3
        return max(time() - date('U'), 0);
240
    }
241
242
    /**
243
     * Set the age of the response
244
     *
245
     * @param  integer $age
246
     *
247
     * @return void
248
     */
249
    public function setAge($age): void
250
    {
251
        $this->response->setHeader('Age', $age);
252
    }
253
254
    /**
255
     * Returns the value of the Expires header as a DateTime instance
256
     *
257
     * @return string A string or null if the header does not exist
258
     */
259 3
    public function getExpires(): string
260
    {
261 3
        return $this->response->getHeader('Expires');
262
    }
263
264
    /**
265
     * Sets the Expires HTTP header with a DateTime instance
266
     *
267
     * @param  \DateTime|string $date A \DateTime instance or date as string
268
     *
269
     * @return void
270
     */
271 1
    public function setExpires($date): void
272
    {
273 1
        if ($date instanceof \DateTime) {
274
            $date = clone $date;
275
        } else {
276 1
            $date = new \DateTime($date);
277
        }
278
279 1
        $date->setTimezone(new \DateTimeZone('UTC'));
280 1
        $this->response->setHeader('Expires', $date->format('D, d M Y H:i:s') . ' GMT');
281 1
    }
282
283
    /**
284
     * Returns the Last-Modified HTTP header as a string
285
     *
286
     * @return string A string or null if the header does not exist
287
     */
288 1
    public function getLastModified(): string
289
    {
290 1
        return $this->response->getHeader('Last-Modified');
291
    }
292
293
    /**
294
     * Sets the Last-Modified HTTP header with a DateTime instance or string
295
     *
296
     * @param  \DateTime|string $date A \DateTime instance or date as string
297
     *
298
     * @return void
299
     * @throws \Exception
300
     */
301 1
    public function setLastModified($date): void
302
    {
303 1
        if ($date instanceof \DateTime) {
304
            $date = clone $date;
305
        } else {
306 1
            $date = new \DateTime($date);
307
        }
308
309 1
        $date->setTimezone(new \DateTimeZone('UTC'));
310 1
        $this->response->setHeader('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT');
311 1
    }
312
313
    /**
314
     * Marks the response stale by setting the Age header to be equal to the maximum age of the response
315
     *
316
     * @return void
317
     */
318
    public function expire(): void
319
    {
320
        if ($this->getTtl() > 0) {
321
            $this->setAge($this->getMaxAge());
322
        }
323
    }
324
}
325