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

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