Cookie::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 18
ccs 9
cts 9
cp 1
crap 1
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace HttpSoft\Cookie;
6
7
use DateTimeInterface;
8
use InvalidArgumentException;
9
10
use function array_map;
11
use function get_class;
12
use function gettype;
13
use function gmdate;
14
use function implode;
15
use function in_array;
16
use function is_int;
17
use function is_numeric;
18
use function is_object;
19
use function is_string;
20
use function preg_match;
21
use function rawurlencode;
22
use function sprintf;
23
use function strtolower;
24
use function strtotime;
25
use function time;
26
use function ucfirst;
27
28
final class Cookie implements CookieInterface
29
{
30
    /**
31
     * @var string
32
     */
33
    private string $name;
34
35
    /**
36
     * @var string
37
     */
38
    private string $value;
39
40
    /**
41
     * @var int
42
     */
43
    private int $expires;
44
45
    /**
46
     * @var string|null
47
     */
48
    private ?string $domain;
49
50
    /**
51
     * @var string|null
52
     */
53
    private ?string $path;
54
55
    /**
56
     * @var bool|null
57
     */
58
    private ?bool $secure;
59
60
    /**
61
     * @var bool|null
62
     */
63
    private ?bool $httpOnly;
64
65
    /**
66
     * @var string|null
67
     */
68
    private ?string $sameSite;
69
70
    /**
71
     * @param string $name the name of the cookie.
72
     * @param string $value the value of the cookie.
73
     * @param DateTimeInterface|int|string|null $expire the time the cookie expire.
74
     * @param string|null $path the set of paths for the cookie.
75
     * @param string|null $domain the set of domains for the cookie.
76
     * @param bool|null $secure whether the cookie should only be transmitted over a secure HTTPS connection.
77
     * @param bool|null $httpOnly whether the cookie can be accessed only through the HTTP protocol.
78
     * @param string|null $sameSite whether the cookie will be available for cross-site requests.
79
     * @throws InvalidArgumentException if one or more arguments are not valid.
80
     */
81 99
    public function __construct(
82
        string $name,
83
        string $value = '',
84
        $expire = null,
85
        ?string $domain = null,
86
        ?string $path = '/',
87
        ?bool $secure = true,
88
        ?bool $httpOnly = true,
89
        ?string $sameSite = self::SAME_SITE_LAX
90
    ) {
91 99
        $this->setName($name);
92 98
        $this->setValue($value);
93 98
        $this->setExpires($expire);
94 98
        $this->setDomain($domain);
95 98
        $this->setPath($path);
96 98
        $this->setSecure($secure);
97 98
        $this->setHttpOnly($httpOnly);
98 98
        $this->setSameSite($sameSite);
99
    }
100
101
    /**
102
     * {@inheritDoc}
103
     */
104 37
    public function getName(): string
105
    {
106 37
        return $this->name;
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112 7
    public function getValue(): string
113
    {
114 7
        return $this->value;
115
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120 2
    public function withValue(string $value): CookieInterface
121
    {
122 2
        if ($value === $this->value) {
123 1
            return $this;
124
        }
125
126 2
        $new = clone $this;
127 2
        $new->setValue($value);
128 2
        return $new;
129
    }
130
131
    /**
132
     * {@inheritDoc}
133
     */
134 7
    public function getMaxAge(): int
135
    {
136 7
        $maxAge = $this->expires - time();
137 7
        return $maxAge > 0 ? $maxAge : 0;
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 8
    public function getExpires(): int
144
    {
145 8
        return $this->expires;
146
    }
147
148
    /**
149
     * {@inheritDoc}
150
     */
151 5
    public function isExpired(): bool
152
    {
153 5
        return (!$this->isSession() && $this->expires < time());
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 2
    public function expire(): CookieInterface
160
    {
161 2
        if ($this->isExpired()) {
162 1
            return $this;
163
        }
164
165 2
        $new = clone $this;
166 2
        $new->expires = time() - 31536001;
167 2
        return $new;
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     *
173
     * @throws InvalidArgumentException if the expire time is not valid.
174
     */
175 12
    public function withExpires($expire = null): CookieInterface
176
    {
177 12
        if ($expire === $this->expires) {
178 2
            return $this;
179
        }
180
181 11
        $new = clone $this;
182 11
        $new->setExpires($expire);
183 4
        return $new;
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189 6
    public function getDomain(): ?string
190
    {
191 6
        return $this->domain;
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197 3
    public function withDomain(?string $domain): CookieInterface
198
    {
199 3
        if ($domain === $this->domain) {
200 2
            return $this;
201
        }
202
203 3
        $new = clone $this;
204 3
        $new->setDomain($domain);
205 3
        return $new;
206
    }
207
208
    /**
209
     * {@inheritDoc}
210
     */
211 6
    public function getPath(): ?string
212
    {
213 6
        return $this->path;
214
    }
215
216
    /**
217
     * {@inheritDoc}
218
     */
219 3
    public function withPath(?string $path): CookieInterface
220
    {
221 3
        if ($path === $this->path) {
222 1
            return $this;
223
        }
224
225 3
        $new = clone $this;
226 3
        $new->setPath($path);
227 3
        return $new;
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233 5
    public function isSecure(): bool
234
    {
235 5
        return $this->secure ?? false;
236
    }
237
238
    /**
239
     * {@inheritDoc}
240
     */
241 2
    public function withSecure(bool $secure = true): CookieInterface
242
    {
243 2
        if ($secure === $this->secure) {
244 1
            return $this;
245
        }
246
247 1
        $new = clone $this;
248 1
        $new->setSecure($secure);
249 1
        return $new;
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 5
    public function isHttpOnly(): bool
256
    {
257 5
        return $this->httpOnly ?? false;
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 2
    public function withHttpOnly(bool $httpOnly = true): CookieInterface
264
    {
265 2
        if ($httpOnly === $this->httpOnly) {
266 1
            return $this;
267
        }
268
269 1
        $new = clone $this;
270 1
        $new->setHttpOnly($httpOnly);
271 1
        return $new;
272
    }
273
274
    /**
275
     * {@inheritDoc}
276
     */
277 6
    public function getSameSite(): ?string
278
    {
279 6
        return $this->sameSite;
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     *
285
     * @throws InvalidArgumentException if the sameSite is not valid.
286
     */
287 4
    public function withSameSite(?string $sameSite): CookieInterface
288
    {
289 4
        if ($sameSite === $this->sameSite) {
290 1
            return $this;
291
        }
292
293 3
        $new = clone $this;
294 3
        $new->setSameSite($sameSite);
295 2
        return $new;
296
    }
297
298
    /**
299
     * {@inheritDoc}
300
     */
301 19
    public function isSession(): bool
302
    {
303 19
        return $this->expires === 0;
304
    }
305
306
    /**
307
     * {@inheritDoc}
308
     */
309 19
    public function __toString(): string
310
    {
311 19
        $cookie = $this->name . '=' . rawurlencode($this->value);
312
313 19
        if (!$this->isSession()) {
314 6
            $cookie .= '; Expires=' . gmdate('D, d-M-Y H:i:s T', $this->expires);
315 6
            $cookie .= '; Max-Age=' . $this->getMaxAge();
316
        }
317
318 19
        if ($this->domain !== null) {
319 3
            $cookie .= '; Domain=' . $this->domain;
320
        }
321
322 19
        if ($this->path !== null) {
323 19
            $cookie .= '; Path=' . $this->path;
324
        }
325
326 19
        if ($this->secure === true) {
327 18
            $cookie .= '; Secure';
328
        }
329
330 19
        if ($this->httpOnly === true) {
331 16
            $cookie .= '; HttpOnly';
332
        }
333
334 19
        if ($this->sameSite !== null) {
335 19
            $cookie .= '; SameSite=' . $this->sameSite;
336
        }
337
338 19
        return $cookie;
339
    }
340
341
    /**
342
     * @param string $name
343
     * @throws InvalidArgumentException if the name is not valid.
344
     */
345 99
    private function setName(string $name): void
346
    {
347 99
        if (empty($name)) {
348 2
            throw new InvalidArgumentException('The cookie name cannot be empty.');
349
        }
350
351 98
        if (!preg_match('/^[a-zA-Z0-9!#$%&\' *+\-.^_`|~]+$/', $name)) {
352 20
            throw new InvalidArgumentException(sprintf(
353 20
                'The cookie name `%s` contains invalid characters; must contain any US-ASCII'
354 20
                . ' characters, except control and separator characters, spaces, or tabs.',
355 20
                $name
356 20
            ));
357
        }
358
359 98
        $this->name = $name;
360
    }
361
362
    /**
363
     * @param string $value
364
     */
365 98
    private function setValue(string $value): void
366
    {
367 98
        $this->value = $value;
368
    }
369
370
    /**
371
     * @param mixed $expire
372
     * @throws InvalidArgumentException if the expire time is not valid.
373
     * @psalm-suppress RiskyTruthyFalsyComparison
374
     */
375 98
    private function setExpires($expire): void
376
    {
377 98
        if ($expire !== null && !is_int($expire) && !is_string($expire) && !$expire instanceof DateTimeInterface) {
378 12
            throw new InvalidArgumentException(sprintf(
379 12
                'The cookie expire time is not valid; must be null, or string,'
380 12
                . ' or integer, or DateTimeInterface instance; received `%s`.',
381 12
                (is_object($expire) ? get_class($expire) : gettype($expire))
382 12
            ));
383
        }
384
385 98
        if (empty($expire)) {
386 95
            $this->expires = 0;
387 95
            return;
388
        }
389
390 9
        if ($expire instanceof DateTimeInterface) {
391 3
            $expire = $expire->format('U');
392 8
        } elseif (!is_numeric($expire)) {
393 4
            $stringExpire = $expire;
394 4
            $expire = strtotime($expire);
395
396 4
            if ($expire === false) {
397 2
                throw new InvalidArgumentException(sprintf(
398 2
                    'The string representation of the cookie expire time `%s` is not valid.',
399 2
                    $stringExpire
400 2
                ));
401
            }
402
        }
403
404 7
        $this->expires = ($expire > 0) ? (int) $expire : 0;
405
    }
406
407
    /**
408
     * @param string|null $domain
409
     * @psalm-suppress RiskyTruthyFalsyComparison
410
     */
411 98
    private function setDomain(?string $domain): void
412
    {
413 98
        $this->domain = empty($domain) ? null : $domain;
414
    }
415
416
    /**
417
     * @param string|null $path
418
     * @psalm-suppress RiskyTruthyFalsyComparison
419
     */
420 98
    private function setPath(?string $path): void
421
    {
422 98
        $this->path = empty($path) ? null : $path;
423
    }
424
425
    /**
426
     * @param bool|null $secure
427
     */
428 98
    private function setSecure(?bool $secure): void
429
    {
430 98
        $this->secure = $secure;
431
    }
432
433
    /**
434
     * @param bool|null $httpOnly
435
     */
436 98
    private function setHttpOnly(?bool $httpOnly): void
437
    {
438 98
        $this->httpOnly = $httpOnly;
439
    }
440
441
    /**
442
     * @param string|null $sameSite
443
     * @throws InvalidArgumentException if the sameSite is not valid.
444
     * @psalm-suppress RiskyTruthyFalsyComparison
445
     */
446 98
    private function setSameSite(?string $sameSite): void
447
    {
448 98
        $sameSite = empty($sameSite) ? null : ucfirst(strtolower($sameSite));
449 98
        $sameSiteValues = [self::SAME_SITE_NONE, self::SAME_SITE_LAX, self::SAME_SITE_STRICT];
450
451 98
        if ($sameSite !== null && !in_array($sameSite, $sameSiteValues, true)) {
452 2
            throw new InvalidArgumentException(sprintf(
453 2
                'The sameSite attribute `%s` is not valid; must be one of (%s).',
454 2
                $sameSite,
455 2
                implode(', ', array_map(static fn($item) => "\"$item\"", $sameSiteValues)),
456 2
            ));
457
        }
458
459 98
        $this->sameSite = $sameSite;
460
    }
461
}
462