Completed
Push — master ( 59abc1...a59857 )
by Beau
35s
created

SetCookie::fromSetCookieString()   C

Complexity

Conditions 12
Paths 35

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 57
rs 6.5115
c 0
b 0
f 0
ccs 24
cts 24
cp 1
cc 12
nc 35
nop 1
crap 12

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Dflydev\FigCookies;
6
7
use DateTime;
8
use DateTimeInterface;
9
use Dflydev\FigCookies\Modifier\SameSite;
10
use function array_shift;
11
use function count;
12
use function explode;
13
use function gmdate;
14
use function implode;
15
use function is_int;
16
use function is_numeric;
17
use function is_string;
18
use function sprintf;
19 31
use function strtolower;
20
use function strtotime;
21 31
use function urlencode;
22 31
23 31
class SetCookie
24
{
25 19
    /** @var string */
26
    private $name;
27 19
    /** @var string|null */
28
    private $value;
29
    /** @var int */
30 3
    private $expires = 0;
31
    /** @var int */
32 3
    private $maxAge = 0;
33
    /** @var string|null */
34
    private $path;
35 2
    /** @var string|null */
36
    private $domain;
37 2
    /** @var bool */
38
    private $secure = false;
39
    /** @var bool */
40
    private $httpOnly = false;
41
    /** @var SameSite|null */
42
    private $sameSite;
43
44
    private function __construct(string $name, ?string $value = null)
45
    {
46
        $this->name  = $name;
47
        $this->value = $value;
48
    }
49
50
    public function getName() : string
51
    {
52
        return $this->name;
53
    }
54
55
    public function getValue() : ?string
56
    {
57
        return $this->value;
58
    }
59
60
    public function getExpires() : int
61
    {
62
        return $this->expires;
63
    }
64
65 29
    public function getMaxAge() : int
66
    {
67 29
        return $this->maxAge;
68
    }
69 29
70
    public function getPath() : ?string
71 29
    {
72
        return $this->path;
73
    }
74 13
75
    public function getDomain() : ?string
76 13
    {
77
        return $this->domain;
78
    }
79
80 13
    public function getSecure() : bool
81 2
    {
82
        return $this->secure;
83
    }
84 11
85
    public function getHttpOnly() : bool
86
    {
87
        return $this->httpOnly;
88 11
    }
89
90
    public function getSameSite() : ?SameSite
91 13
    {
92
        return $this->sameSite;
93 13
    }
94
95 13
    public function withValue(?string $value = null) : self
96
    {
97 13
        $clone = clone($this);
98
99 13
        $clone->value = $value;
100
101
        return $clone;
102 1
    }
103
104 1
    /** @param int|\DateTimeInterface|string|null $expires */
105
    private function resolveExpires($expires = null) : int
106
    {
107 1
        if ($expires === null) {
108
            return 0;
109 1
        }
110
111
        if ($expires instanceof DateTimeInterface) {
112 4
            return $expires->getTimestamp();
113
        }
114 4
115
        if (is_numeric($expires)) {
116 4
            return (int) $expires;
117
        }
118 4
119
        $time = strtotime($expires);
120
121 12
        if (! is_int($time)) {
122
            throw new \InvalidArgumentException(sprintf('Invalid expires "%s" provided', $expires));
123 12
        }
124
125 12
        return $time;
126
    }
127 12
128
    /** @param int|string|\DateTimeInterface|null $expires */
129
    public function withExpires($expires = null) : self
130 7
    {
131
        $expires = $this->resolveExpires($expires);
132 7
133
        $clone = clone($this);
134 7
135
        $clone->expires = $expires;
136 7
137
        return $clone;
138
    }
139 10
140
    public function rememberForever() : self
141 10
    {
142
        return $this->withExpires(new DateTime('+5 years'));
143 10
    }
144
145 10
    public function expire() : self
146
    {
147
        return $this->withExpires(new DateTime('-5 years'));
148 12
    }
149
150 12
    public function withMaxAge(?int $maxAge = null) : self
151
    {
152 12
        $clone = clone($this);
153
154 12
        $clone->maxAge = (int) $maxAge;
155
156
        return $clone;
157 17
    }
158
159
    public function withPath(?string $path = null) : self
160 17
    {
161 17
        $clone = clone($this);
162
163 17
        $clone->path = $path;
164 17
165 17
        return $clone;
166 17
    }
167 17
168 17
    public function withDomain(?string $domain = null) : self
169
    {
170 17
        $clone = clone($this);
171
172
        $clone->domain = $domain;
173 8
174
        return $clone;
175 8
    }
176
177
    public function withSecure(bool $secure = true) : self
178 1
    {
179
        $clone = clone($this);
180 1
181
        $clone->secure = $secure;
182
183 1
        return $clone;
184
    }
185 1
186
    public function withHttpOnly(bool $httpOnly = true) : self
187
    {
188 29
        $clone = clone($this);
189
190 29
        $clone->httpOnly = $httpOnly;
191
192 29
        return $clone;
193
    }
194
195 29
    public function withSameSite(SameSite $sameSite) : self
196
    {
197 29
        $clone = clone($this);
198 29
199 29
        $clone->sameSite = $sameSite;
200
201 29
        return $clone;
202 12
    }
203
204 12
    public function withoutSameSite() : self
205 12
    {
206
        $clone = clone($this);
207 12
208
        $clone->sameSite = null;
209
210 12
        return $clone;
211 11
    }
212 11
213 12
    public function __toString() : string
214 4
    {
215 4
        $cookieStringParts = [
216 12
            urlencode($this->name) . '=' . urlencode((string) $this->value),
217 7
        ];
218 7
219 12
        $cookieStringParts = $this->appendFormattedDomainPartIfSet($cookieStringParts);
220 12
        $cookieStringParts = $this->appendFormattedPathPartIfSet($cookieStringParts);
221 12
        $cookieStringParts = $this->appendFormattedExpiresPartIfSet($cookieStringParts);
222 12
        $cookieStringParts = $this->appendFormattedMaxAgePartIfSet($cookieStringParts);
223 10
        $cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts);
224 10
        $cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts);
225 12
        $cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts);
226 12
227 12
        return implode('; ', $cookieStringParts);
228
    }
229
230 12
    public static function create(string $name, ?string $value = null) : self
231
    {
232 29
        return new static($name, $value);
233
    }
234 17
235
    public static function createRememberedForever(string $name, ?string $value = null) : self
236 17
    {
237 7
        return static::create($name, $value)->rememberForever();
238 7
    }
239
240 17
    public static function createExpired(string $name) : self
241
    {
242
        return static::create($name)->expire();
243 17
    }
244
245 17
    public static function fromSetCookieString(string $string) : self
246 8
    {
247 8
        $rawAttributes = StringUtil::splitOnAttributeDelimiter($string);
248
249 17
        $rawAttribute = array_shift($rawAttributes);
250
251
        if (! is_string($rawAttribute)) {
252 17
            throw new \InvalidArgumentException(sprintf(
253
                'The provided cookie string "%s" must have at least one attribute',
254 17
                $string
255 7
            ));
256 7
        }
257
258 17
        list ($cookieName, $cookieValue) = StringUtil::splitCookiePair($rawAttribute);
259
260
        /** @var SetCookie $setCookie */
261 17
        $setCookie = new static($cookieName);
262
263 17
        if ($cookieValue !== null) {
264 4
            $setCookie = $setCookie->withValue($cookieValue);
265 4
        }
266
267 17
        while ($rawAttribute = array_shift($rawAttributes)) {
268
            $rawAttributePair = explode('=', $rawAttribute, 2);
269
270 17
            $attributeKey   = $rawAttributePair[0];
271
            $attributeValue = count($rawAttributePair) > 1 ? $rawAttributePair[1] : null;
272 17
273 6
            $attributeKey = strtolower($attributeKey);
274 6
275
            switch ($attributeKey) {
276 17
                case 'expires':
277
                    $setCookie = $setCookie->withExpires($attributeValue);
278
                    break;
279 17
                case 'max-age':
280
                    $setCookie = $setCookie->withMaxAge((int) $attributeValue);
281 17
                    break;
282 8
                case 'domain':
283 8
                    $setCookie = $setCookie->withDomain($attributeValue);
284
                    break;
285 17
                case 'path':
286
                    $setCookie = $setCookie->withPath($attributeValue);
287
                    break;
288
                case 'secure':
289
                    $setCookie = $setCookie->withSecure(true);
290
                    break;
291
                case 'httponly':
292
                    $setCookie = $setCookie->withHttpOnly(true);
293
                    break;
294
                case 'samesite':
295
                    $setCookie = $setCookie->withSameSite(SameSite::fromString((string) $attributeValue));
296
                    break;
297
            }
298
        }
299
300
        return $setCookie;
301
    }
302
303
    /**
304
     * @param string[] $cookieStringParts
305
     *
306
     * @return string[]
307
     */
308
    private function appendFormattedDomainPartIfSet(array $cookieStringParts) : array
309
    {
310
        if ($this->domain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->domain of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
311
            $cookieStringParts[] = sprintf('Domain=%s', $this->domain);
312
        }
313
314
        return $cookieStringParts;
315
    }
316
317
    /**
318
     * @param string[] $cookieStringParts
319
     *
320
     * @return string[]
321
     */
322
    private function appendFormattedPathPartIfSet(array $cookieStringParts) : array
323
    {
324
        if ($this->path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->path of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
325
            $cookieStringParts[] = sprintf('Path=%s', $this->path);
326
        }
327
328
        return $cookieStringParts;
329
    }
330
331
    /**
332
     * @param string[] $cookieStringParts
333
     *
334
     * @return string[]
335
     */
336
    private function appendFormattedExpiresPartIfSet(array $cookieStringParts) : array
337
    {
338
        if ($this->expires) {
339
            $cookieStringParts[] = sprintf('Expires=%s', gmdate('D, d M Y H:i:s T', $this->expires));
340
        }
341
342
        return $cookieStringParts;
343
    }
344
345
    /**
346
     * @param string[] $cookieStringParts
347
     *
348
     * @return string[]
349
     */
350
    private function appendFormattedMaxAgePartIfSet(array $cookieStringParts) : array
351
    {
352
        if ($this->maxAge) {
353
            $cookieStringParts[] = sprintf('Max-Age=%s', $this->maxAge);
354
        }
355
356
        return $cookieStringParts;
357
    }
358
359
    /**
360
     * @param string[] $cookieStringParts
361
     *
362
     * @return string[]
363
     */
364
    private function appendFormattedSecurePartIfSet(array $cookieStringParts) : array
365
    {
366
        if ($this->secure) {
367
            $cookieStringParts[] = 'Secure';
368
        }
369
370
        return $cookieStringParts;
371
    }
372
373
    /**
374
     * @param string[] $cookieStringParts
375
     *
376
     * @return string[]
377
     */
378
    private function appendFormattedHttpOnlyPartIfSet(array $cookieStringParts) : array
379
    {
380
        if ($this->httpOnly) {
381
            $cookieStringParts[] = 'HttpOnly';
382
        }
383
384
        return $cookieStringParts;
385
    }
386
387
    /**
388
     * @param string[] $cookieStringParts
389
     *
390
     * @return string[]
391
     */
392
    private function appendFormattedSameSitePartIfSet(array $cookieStringParts) : array
393
    {
394
        if ($this->sameSite === null) {
395
            return $cookieStringParts;
396
        }
397
398
        $cookieStringParts[] = $this->sameSite->asString();
399
400
        return $cookieStringParts;
401
    }
402
}
403