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

Cookie::hasDomain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

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 1
eloc 2
nc 1
nop 0
crap 1
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 integer|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 boolean
46
     */
47
    protected $secure;
48
49
    /**
50
     * @var boolean
51
     */
52
    protected $httpOnly;
53
54
    /**
55
     * @param string                 $name
56
     * @param string|null            $value
57
     * @param integer|\DateTime|null $expiration
58
     * @param string|null            $domain
59
     * @param string|null            $path
60
     * @param boolean                $secure
61
     * @param boolean                $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 boolean
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 self
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 integer|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 boolean
148
     */
149 5
    public function hasMaxAge()
150
    {
151 5
        return isset($this->maxAge);
152
    }
153
154
    /**
155
     * Sets the max age
156
     *
157
     * @param integer|null $maxAge
158
     *
159
     * @return self
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 integer|\DateTime|null $expiration
173
     *
174
     * @return self
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 boolean
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 self
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 boolean
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 boolean
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 self
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
     * Matches a domain
267
     *
268
     * @param string $domain
269
     *
270
     * @return boolean
271
     *
272
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
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 self
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
     * It matches a path
316
     *
317
     * @param string $path
318
     *
319
     * @return boolean
320
     *
321
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
322
     */
323 2
    public function matchPath($path)
324
    {
325 2
        return $this->path === $path || (strpos($path, $this->path.'/') === 0);
326
    }
327
328
    /**
329
     * Checks if HTTPS is required
330
     *
331
     * @return boolean
332
     */
333 2
    public function isSecure()
334
    {
335 2
        return $this->secure;
336
    }
337
338
    /**
339
     * Sets the secure
340
     *
341
     * @param boolean $secure
342
     *
343
     * @return self
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
     * Checks if it is HTTP-only
355
     *
356
     * @return boolean
357
     */
358 2
    public function isHttpOnly()
359
    {
360 2
        return $this->httpOnly;
361
    }
362
363
    /**
364
     * Sets the HTTP Only
365
     *
366
     * @param boolean $httpOnly
367
     *
368
     * @return self
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 it matches with another cookie
380
     *
381
     * @param Cookie $cookie
382
     *
383
     * @return boolean
384
     */
385 10
    public function match(Cookie $cookie)
386
    {
387 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...
388
    }
389
390
    /**
391
     * Validates the name attribute
392
     *
393
     * @param string $name
394
     *
395
     * @throws \InvalidArgumentException
396
     *
397
     * @see http://tools.ietf.org/search/rfc2616#section-2.2
398
     */
399 40
    private function validateName($name)
400
    {
401 40
        if (strlen($name) < 1) {
402 1
            throw new \InvalidArgumentException('The name cannot be empty');
403
        }
404
405
        // Name attribute is a token as per spec in RFC 2616
406 39
        if (preg_match('/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5b-\x5d\x7b\x7d\x7f]/', $name)) {
407 1
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
408
        }
409 39
    }
410
411
    /**
412
     * Validates a value
413
     *
414
     * @param string|null $value
415
     *
416
     * @throws \InvalidArgumentException
417
     *
418
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
419
     */
420 39
    private function validateValue($value)
421
    {
422 39
        if (isset($value)) {
423 38
            if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
424 2
                throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
425
            }
426 38
        }
427 39
    }
428
429
    /**
430
     * Normalizes the expiration value
431
     *
432
     * @param integer|\DateTime|null $expiration
433
     *
434
     * @return \DateTime|null
435
     */
436 39
    private function normalizeExpires($expiration)
437
    {
438 39
        $expires = null;
439
440 39
        if (is_int($expiration)) {
441 33
            $expires = new \DateTime(sprintf('%d seconds', $expiration));
442
443
            // According to RFC 2616 date should be set to earliest representable date
444 33
            if ($expiration <= 0) {
445 30
                $expires->setTimestamp(-PHP_INT_MAX);
446 30
            }
447 39
        } elseif ($expiration instanceof \DateTime) {
448 1
            $expires = $expiration;
449 1
        }
450
451 39
        return $expires;
452
    }
453
454
    /**
455
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265
456
     *
457
     * @param string|null $domain
458
     *
459
     * @return string
460
     *
461
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
462
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
463
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
464
     */
465 39
    private function normalizeDomain($domain)
466
    {
467 39
        if (isset($domain)) {
468 6
            $domain = ltrim(strtolower($domain), '.');
469 6
        }
470
471 39
        return $domain;
472
    }
473
474
    /**
475
     * Processes path as per spec in RFC 6265
476
     *
477
     * @param string|null $path
478
     *
479
     * @return string
480
     *
481
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
482
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
483
     */
484 39
    private function normalizePath($path)
485
    {
486 39
        $path = rtrim($path, '/');
487
488 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...
489 38
            $path = '/';
490 38
        }
491
492 39
        return $path;
493
    }
494
}
495