Completed
Pull Request — master (#68)
by Antonio J.
07:16
created

Cookie::hasExpires()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
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
    private $name;
18
19
    /**
20
     * @var string|null
21
     */
22
    private $value;
23
24
    /**
25
     * @var int|null
26
     */
27
    private $maxAge;
28
29
    /**
30
     * @var string|null
31
     */
32
    private $domain;
33
34
    /**
35
     * @var string
36
     */
37
    private $path;
38
39
    /**
40
     * @var bool
41
     */
42
    private $secure;
43
44
    /**
45
     * @var bool
46
     */
47
    private $httpOnly;
48
49
    /**
50
     * Expires attribute is HTTP 1.0 only and should be avoided.
51
     *
52
     * @var \DateTime|null
53
     */
54
    private $expires;
55
56
    /**
57
     * @param string         $name
58
     * @param string|null    $value
59
     * @param int            $maxAge
60
     * @param string|null    $domain
61
     * @param string|null    $path
62
     * @param bool           $secure
63
     * @param bool           $httpOnly
64
     * @param \DateTime|null $expires  Expires attribute is HTTP 1.0 only and should be avoided.
65
     *
66
     * @throws \InvalidArgumentException If name, value or max age is not valid.
67
     */
68 57
    public function __construct(
69
        $name,
70
        $value = null,
71
        $maxAge = null,
72
        $domain = null,
73
        $path = null,
74
        $secure = false,
75
        $httpOnly = false,
76
        \DateTime $expires = null
77
    ) {
78 57
        $this->validateName($name);
79 53
        $this->validateValue($value);
80 50
        $this->validateMaxAge($maxAge);
81
82 50
        $this->name = $name;
83 50
        $this->value = $value;
84 50
        $this->maxAge = $maxAge;
85 50
        $this->expires = $expires;
86 50
        $this->domain = $this->normalizeDomain($domain);
87 50
        $this->path = $this->normalizePath($path);
88 50
        $this->secure = (bool) $secure;
89 50
        $this->httpOnly = (bool) $httpOnly;
90 50
    }
91
92
    /**
93
     * Creates a new cookie without any attribute validation.
94
     *
95
     * @param string         $name
96
     * @param string|null    $value
97 2
     * @param int            $maxAge
98
     * @param string|null    $domain
99 2
     * @param string|null    $path
100
     * @param bool           $secure
101
     * @param bool           $httpOnly
102
     * @param \DateTime|null $expires  Expires attribute is HTTP 1.0 only and should be avoided.
103
     */
104
    public static function createWithoutValidation(
105
        $name,
106
        $value = null,
107 5
        $maxAge = null,
108
        $domain = null,
109 5
        $path = null,
110
        $secure = false,
111
        $httpOnly = false,
112
        \DateTime $expires = null
113
    ) {
114
        $cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires);
115
        $cookie->name = $name;
116
        $cookie->value = $value;
117 13
        $cookie->maxAge = $maxAge;
118
119 13
        return $cookie;
120
    }
121
122
    /**
123
     * Returns the name.
124
     *
125
     * @return string
126
     */
127
    public function getName()
128
    {
129 8
        return $this->name;
130
    }
131 8
132
    /**
133 5
     * Returns the value.
134 5
     *
135
     * @return string|null
136 5
     */
137
    public function getValue()
138
    {
139
        return $this->value;
140
    }
141
142
    /**
143
     * Checks if there is a value.
144 3
     *
145
     * @return bool
146 3
     */
147
    public function hasValue()
148
    {
149
        return isset($this->value);
150
    }
151
152
    /**
153
     * Sets the value.
154 1
     *
155
     * @param string|null $value
156 1
     *
157
     * @return Cookie
158
     */
159
    public function withValue($value)
160
    {
161
        $this->validateValue($value);
162
163
        $new = clone $this;
164
        $new->value = $value;
165
166 2
        return $new;
167
    }
168 2
169
    /**
170 1
     * Returns the max age.
171 1
     *
172
     * @return int|null
173 1
     */
174
    public function getMaxAge()
175
    {
176
        return $this->maxAge;
177
    }
178
179
    /**
180
     * Checks if there is a max age.
181 3
     *
182
     * @return bool
183 3
     */
184
    public function hasMaxAge()
185
    {
186
        return isset($this->maxAge);
187
    }
188
189
    /**
190
     * Sets the max age.
191 2
     *
192
     * @param int|null $maxAge
193 2
     *
194
     * @return Cookie
195
     */
196
    public function withMaxAge($maxAge)
197
    {
198
        $this->validateMaxAge($maxAge);
199
200
        $new = clone $this;
201
        $new->maxAge = $maxAge;
202
203 1
        return $new;
204
    }
205 1
206 1
    /**
207
     * Returns the expiration time.
208 1
     *
209
     * @return \DateTime|null
210
     */
211
    public function getExpires()
212
    {
213
        return $this->expires;
214
    }
215
216 2
    /**
217
     * Checks if there is an expiration time.
218 2
     *
219
     * @return bool
220
     */
221
    public function hasExpires()
222
    {
223
        return isset($this->expires);
224
    }
225
226 3
    /**
227
     * Sets the expires.
228 3
     *
229
     * @param \DateTime|null $expires
230
     *
231
     * @return Cookie
232
     */
233
    public function withExpires(\DateTime $expires = null)
234
    {
235
        $new = clone $this;
236 4
        $new->expires = $expires;
237
238 4
        return $new;
239
    }
240
241
    /**
242
     * Checks if the cookie is expired.
243
     *
244
     * @return bool
245
     */
246
    public function isExpired()
247
    {
248 1
        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...
249
    }
250 1
251 1
    /**
252
     * Returns the domain.
253 1
     *
254
     * @return string|null
255
     */
256
    public function getDomain()
257
    {
258
        return $this->domain;
259
    }
260
261
    /**
262
     * Checks if there is a domain.
263
     *
264
     * @return bool
265 2
     */
266
    public function hasDomain()
267
    {
268 2
        return isset($this->domain);
269 2
    }
270
271
    /**
272
     * Sets the domain.
273 1
     *
274 1
     * @param string|null $domain
275
     *
276
     * @return Cookie
277 1
     */
278
    public function withDomain($domain)
279
    {
280
        $new = clone $this;
281
        $new->domain = $this->normalizeDomain($domain);
282
283
        return $new;
284
    }
285 2
286
    /**
287 2
     * Checks whether this cookie is meant for this domain.
288
     *
289
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
290
     *
291
     * @param string $domain
292
     *
293
     * @return bool
294
     */
295
    public function matchDomain($domain)
296
    {
297 1
        // Domain is not set or exact match
298
        if (!$this->hasDomain() || strcasecmp($domain, $this->domain) === 0) {
299 1
            return true;
300 1
        }
301
302 1
        // Domain is not an IP address
303
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
304
            return false;
305
        }
306
307
        return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain);
308
    }
309
310
    /**
311
     * Returns the path.
312
     *
313
     * @return string
314 3
     */
315
    public function getPath()
316 3
    {
317
        return $this->path;
318
    }
319
320
    /**
321
     * Sets the path.
322
     *
323
     * @param string|null $path
324 2
     *
325
     * @return Cookie
326 2
     */
327
    public function withPath($path)
328
    {
329
        $new = clone $this;
330
        $new->path = $this->normalizePath($path);
331
332
        return $new;
333
    }
334
335
    /**
336 1
     * Checks whether this cookie is meant for this path.
337
     *
338 1
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
339 1
     *
340
     * @param string $path
341 1
     *
342
     * @return bool
343
     */
344
    public function matchPath($path)
345
    {
346
        return $this->path === $path || (strpos($path, rtrim($this->path, '/').'/') === 0);
347
    }
348
349 2
    /**
350
     * Checks whether this cookie may only be sent over HTTPS.
351 2
     *
352
     * @return bool
353
     */
354
    public function isSecure()
355
    {
356
        return $this->secure;
357
    }
358
359
    /**
360
     * Sets whether this cookie should only be sent over HTTPS.
361 1
     *
362
     * @param bool $secure
363 1
     *
364 1
     * @return Cookie
365
     */
366 1
    public function withSecure($secure)
367
    {
368
        $new = clone $this;
369
        $new->secure = (bool) $secure;
370
371
        return $new;
372
    }
373
374
    /**
375
     * Check whether this cookie may not be accessed through Javascript.
376
     *
377
     * @return bool
378 10
     */
379
    public function isHttpOnly()
380 10
    {
381
        return $this->httpOnly;
382
    }
383
384
    /**
385
     * Sets whether this cookie may not be accessed through Javascript.
386
     *
387
     * @param bool $httpOnly
388
     *
389
     * @return Cookie
390
     */
391
    public function withHttpOnly($httpOnly)
392 57
    {
393
        $new = clone $this;
394 57
        $new->httpOnly = (bool) $httpOnly;
395 1
396
        return $new;
397
    }
398
399 56
    /**
400 3
     * Checks if this cookie represents the same cookie as $cookie.
401
     *
402 53
     * This does not compare the values, only name, domain and path.
403
     *
404
     * @param Cookie $cookie
405
     *
406
     * @return bool
407
     */
408
    public function match(Cookie $cookie)
409
    {
410
        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...
411
    }
412
413 53
    /**
414
     * Validates cookie attributes.
415 53
     *
416 49
     * @return bool
417 6
     */
418
    public function isValid()
419 46
    {
420 50
        try {
421
            $this->validateName($this->name);
422
            $this->validateValue($this->value);
423
            $this->validateMaxAge($this->maxAge);
424
        } catch (\InvalidArgumentException $e) {
425
            return false;
426
        }
427
428
        return true;
429 50
    }
430
431 50
    /**
432 7
     * Validates the name attribute.
433 1
     *
434
     * @see http://tools.ietf.org/search/rfc2616#section-2.2
435 6
     *
436 50
     * @param string $name
437
     *
438
     * @throws \InvalidArgumentException If the name is empty or contains invalid characters.
439
     */
440
    private function validateName($name)
441
    {
442
        if (strlen($name) < 1) {
443
            throw new \InvalidArgumentException('The name cannot be empty');
444
        }
445
446
        // Name attribute is a token as per spec in RFC 2616
447
        if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) {
448
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
449 50
        }
450
    }
451 50
452 6
    /**
453 6
     * Validates a value.
454
     *
455 50
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
456
     *
457
     * @param string|null $value
458
     *
459
     * @throws \InvalidArgumentException If the value contains invalid characters.
460
     */
461
    private function validateValue($value)
462
    {
463
        if (isset($value)) {
464
            if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
465
                throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
466
            }
467
        }
468 50
    }
469
470 50
    /**
471
     * Validates a Max-Age attribute.
472 50
     *
473 49
     * @param int|null $maxAge
474 49
     *
475
     * @throws \InvalidArgumentException If the Max-Age is not an empty or integer value.
476 50
     */
477
    private function validateMaxAge($maxAge)
478
    {
479
        if (isset($maxAge)) {
480
            if (!is_int($maxAge)) {
481
                throw new \InvalidArgumentException('Max-Age must be integer');
482
            }
483
        }
484
    }
485
486
    /**
487
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
488
     *
489
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
490
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
491
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
492
     *
493
     * @param string|null $domain
494
     *
495
     * @return string
496
     */
497
    private function normalizeDomain($domain)
498
    {
499
        if (isset($domain)) {
500
            $domain = ltrim(strtolower($domain), '.');
501
        }
502
503
        return $domain;
504
    }
505
506
    /**
507
     * Processes path as per spec in RFC 6265.
508
     *
509
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
510
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
511
     *
512
     * @param string|null $path
513
     *
514
     * @return string
515
     */
516
    private function normalizePath($path)
517
    {
518
        $path = rtrim($path, '/');
519
520
        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...
521
            $path = '/';
522
        }
523
524
        return $path;
525
    }
526
}
527