Completed
Pull Request — master (#22)
by Márk
03:03
created

Cookie::isExpired()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 2
eloc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Http\Message;
4
5
/**
6
 * Cookie Value Object.
7
 *
8
 * @author Márk Sági-Kazár <[email protected]>
9
 *
10
 * @see http://tools.ietf.org/search/rfc6265
11
 */
12
final class Cookie
13
{
14
    /**
15
     * @var string
16
     */
17
    protected $name;
18
19
    /**
20
     * @var string|null
21
     */
22
    protected $value;
23
24
    /**
25
     * @var int|null
26
     */
27
    protected $maxAge;
28
29
    /**
30
     * @var \DateTime|null
31
     */
32
    protected $expires;
33
34
    /**
35
     * @var string|null
36
     */
37
    protected $domain;
38
39
    /**
40
     * @var string
41
     */
42
    protected $path;
43
44
    /**
45
     * @var bool
46
     */
47
    protected $secure;
48
49
    /**
50
     * @var bool
51
     */
52
    protected $httpOnly;
53
54
    /**
55
     * @param string             $name
56
     * @param string|null        $value
57
     * @param int|\DateTime|null $expiration
58
     * @param string|null        $domain
59
     * @param string|null        $path
60
     * @param bool               $secure
61
     * @param bool               $httpOnly
62
     *
63
     * @throws \InvalidArgumentException
64
     */
65 40
    public function __construct(
66
        $name,
67
        $value = null,
68
        $expiration = 0,
69
        $domain = null,
70
        $path = null,
71
        $secure = false,
72
        $httpOnly = false
73
    ) {
74 40
        $this->validateName($name);
75 39
        $this->validateValue($value);
76
77 39
        $this->name = $name;
78 39
        $this->value = $value;
79 39
        $this->maxAge = is_int($expiration) ? $expiration : null;
80 39
        $this->expires = $this->normalizeExpires($expiration);
81 39
        $this->domain = $this->normalizeDomain($domain);
82 39
        $this->path = $this->normalizePath($path);
83 39
        $this->secure = (bool) $secure;
84 39
        $this->httpOnly = (bool) $httpOnly;
85 39
    }
86
87
    /**
88
     * Returns the name.
89
     *
90
     * @return string
91
     */
92 2
    public function getName()
93
    {
94 2
        return $this->name;
95
    }
96
97
    /**
98
     * Returns the value.
99
     *
100
     * @return string|null
101
     */
102 5
    public function getValue()
103
    {
104 5
        return $this->value;
105
    }
106
107
    /**
108
     * Checks if there is a value.
109
     *
110
     * @return bool
111
     */
112 13
    public function hasValue()
113
    {
114 13
        return isset($this->value);
115
    }
116
117
    /**
118
     * Sets the value.
119
     *
120
     * @param string|null $value
121
     *
122
     * @return Cookie
123
     */
124 2
    public function withValue($value)
125
    {
126 2
        $this->validateValue($value);
127
128 2
        $new = clone $this;
129 2
        $new->value = $value;
130
131 2
        return $new;
132
    }
133
134
    /**
135
     * Returns the max age.
136
     *
137
     * @return int|null
138
     */
139 8
    public function getMaxAge()
140
    {
141 8
        return $this->maxAge;
142
    }
143
144
    /**
145
     * Checks if there is a max age.
146
     *
147
     * @return bool
148
     */
149 5
    public function hasMaxAge()
150
    {
151 5
        return isset($this->maxAge);
152
    }
153
154
    /**
155
     * Sets the max age.
156
     *
157
     * @param int|null $maxAge
158
     *
159
     * @return Cookie
160
     */
161 1
    public function withMaxAge($maxAge)
162
    {
163 1
        $new = clone $this;
164 1
        $new->maxAge = is_int($maxAge) ? $maxAge : null;
165
166 1
        return $new;
167
    }
168
169
    /**
170
     * Sets both the max age and the expires attributes.
171
     *
172
     * @param int|\DateTime|null $expiration
173
     *
174
     * @return Cookie
175
     */
176 1
    public function withExpiration($expiration)
177
    {
178 1
        $new = clone $this;
179 1
        $new->maxAge = is_int($expiration) ? $expiration : null;
180 1
        $new->expires = $this->normalizeExpires($expiration);
181
182 1
        return $new;
183
    }
184
185
    /**
186
     * Returns the expiration time.
187
     *
188
     * @return \DateTime|null
189
     */
190 9
    public function getExpires()
191
    {
192 9
        return $this->expires;
193
    }
194
195
    /**
196
     * Checks if there is an expiration time.
197
     *
198
     * @return bool
199
     */
200 5
    public function hasExpires()
201
    {
202 5
        return isset($this->expires);
203
    }
204
205
    /**
206
     * Sets the expires.
207
     *
208
     * @param \DateTime|null $expires
209
     *
210
     * @return Cookie
211
     */
212 1
    public function withExpires(\DateTime $expires)
213
    {
214 1
        $new = clone $this;
215 1
        $new->expires = $expires;
216
217 1
        return $new;
218
    }
219
220
    /**
221
     * Checks if the cookie is expired.
222
     *
223
     * @return bool
224
     */
225 5
    public function isExpired()
226
    {
227 5
        return isset($this->expires) and $this->expires < new \DateTime();
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
228
    }
229
230
    /**
231
     * Returns the domain.
232
     *
233
     * @return string|null
234
     */
235 3
    public function getDomain()
236
    {
237 3
        return $this->domain;
238
    }
239
240
    /**
241
     * Checks if there is a domain.
242
     *
243
     * @return bool
244
     */
245 4
    public function hasDomain()
246
    {
247 4
        return isset($this->domain);
248
    }
249
250
    /**
251
     * Sets the domain.
252
     *
253
     * @param string|null $domain
254
     *
255
     * @return Cookie
256
     */
257 1
    public function withDomain($domain)
258
    {
259 1
        $new = clone $this;
260 1
        $new->domain = $this->normalizeDomain($domain);
261
262 1
        return $new;
263
    }
264
265
    /**
266
     * Checks whether this cookie is meant for this domain.
267
     *
268
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
269
     *
270
     * @param string $domain
271
     *
272
     * @return bool
273
     */
274 2
    public function matchDomain($domain)
275
    {
276
        // Domain is not set or exact match
277 2
        if (!$this->hasDomain() || strcasecmp($domain, $this->domain) === 0) {
278 2
            return true;
279
        }
280
281
        // Domain is not an IP address
282 1
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
283 1
            return false;
284
        }
285
286 1
        return (bool) preg_match('/\b'.preg_quote($this->domain).'$/i', $domain);
287
    }
288
289
    /**
290
     * Returns the path.
291
     *
292
     * @return string
293
     */
294 2
    public function getPath()
295
    {
296 2
        return $this->path;
297
    }
298
299
    /**
300
     * Sets the path.
301
     *
302
     * @param string|null $path
303
     *
304
     * @return Cookie
305
     */
306 1
    public function withPath($path)
307
    {
308 1
        $new = clone $this;
309 1
        $new->path = $this->normalizePath($path);
310
311 1
        return $new;
312
    }
313
314
    /**
315
     * Checks whether this cookie is meant for this path.
316
     *
317
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
318
     *
319
     * @param string $path
320
     *
321
     * @return bool
322
     */
323 2
    public function matchPath($path)
324
    {
325 2
        return $this->path === $path || (strpos($path, $this->path.'/') === 0);
326
    }
327
328
    /**
329
     * Checks whether this cookie may only be sent over HTTPS.
330
     *
331
     * @return bool
332
     */
333 2
    public function isSecure()
334
    {
335 2
        return $this->secure;
336
    }
337
338
    /**
339
     * Sets whether this cookie should only be sent over HTTPS.
340
     *
341
     * @param bool $secure
342
     *
343
     * @return Cookie
344
     */
345 1
    public function withSecure($secure)
346
    {
347 1
        $new = clone $this;
348 1
        $new->secure = (bool) $secure;
349
350 1
        return $new;
351
    }
352
353
    /**
354
     * Check whether this cookie may not be accessed through Javascript.
355
     *
356
     * @return bool
357
     */
358 2
    public function isHttpOnly()
359
    {
360 2
        return $this->httpOnly;
361
    }
362
363
    /**
364
     * Sets whether this cookie may not be accessed through Javascript.
365
     *
366
     * @param bool $httpOnly
367
     *
368
     * @return Cookie
369
     */
370 1
    public function withHttpOnly($httpOnly)
371
    {
372 1
        $new = clone $this;
373 1
        $new->httpOnly = (bool) $httpOnly;
374
375 1
        return $new;
376
    }
377
378
    /**
379
     * Checks if this cookie represents the same cookie as $cookie.
380
     *
381
     * This does not compare the values, only name, domain and path.
382
     *
383
     * @param Cookie $cookie
384
     *
385
     * @return bool
386
     */
387 10
    public function match(Cookie $cookie)
388
    {
389 10
        return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
390
    }
391
392
    /**
393
     * Validates the name attribute.
394
     *
395
     * @see http://tools.ietf.org/search/rfc2616#section-2.2
396
     *
397
     * @param string $name
398
     *
399
     * @throws \InvalidArgumentException If the name is empty or contains invalid characters.
400
     */
401 40
    private function validateName($name)
402
    {
403 40
        if (strlen($name) < 1) {
404 1
            throw new \InvalidArgumentException('The name cannot be empty');
405
        }
406
407
        // Name attribute is a token as per spec in RFC 2616
408 39
        if (preg_match('/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5b-\x5d\x7b\x7d\x7f]/', $name)) {
409 1
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
410
        }
411 39
    }
412
413
    /**
414
     * Validates a value.
415
     *
416
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
417
     *
418
     * @param string|null $value
419
     *
420
     * @throws \InvalidArgumentException If the value contains invalid characters.
421
     */
422 39
    private function validateValue($value)
423
    {
424 39
        if (isset($value)) {
425 38
            if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
426 2
                throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
427
            }
428 38
        }
429 39
    }
430
431
    /**
432
     * Normalizes the expiration value.
433
     *
434
     * @param int|\DateTime|null $expiration
435
     *
436
     * @return \DateTime|null
437
     */
438 39
    private function normalizeExpires($expiration)
439
    {
440 39
        $expires = null;
441
442 39
        if (is_int($expiration)) {
443 33
            $expires = new \DateTime(sprintf('%d seconds', $expiration));
444
445
            // According to RFC 2616 date should be set to earliest representable date
446 33
            if ($expiration <= 0) {
447 30
                $expires->setTimestamp(-PHP_INT_MAX);
448 30
            }
449 39
        } elseif ($expiration instanceof \DateTime) {
450 1
            $expires = $expiration;
451 1
        }
452
453 39
        return $expires;
454
    }
455
456
    /**
457
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
458
     *
459
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
460
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
461
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
462
     *
463
     * @param string|null $domain
464
     *
465
     * @return string
466
     */
467 39
    private function normalizeDomain($domain)
468
    {
469 39
        if (isset($domain)) {
470 6
            $domain = ltrim(strtolower($domain), '.');
471 6
        }
472
473 39
        return $domain;
474
    }
475
476
    /**
477
     * Processes path as per spec in RFC 6265.
478
     *
479
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
480
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
481
     *
482
     * @param string|null $path
483
     *
484
     * @return string
485
     */
486 39
    private function normalizePath($path)
487
    {
488 39
        $path = rtrim($path, '/');
489
490 39
        if (empty($path) or substr($path, 0, 1) !== '/') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
491 38
            $path = '/';
492 38
        }
493
494 39
        return $path;
495
    }
496
}
497