Passed
Push — master ( d2382c...8b72b7 )
by Alexander
02:48
created

src/CookieCollection.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web;
6
7
use ArrayAccess;
8
use ArrayIterator;
9
use Closure;
10
use Countable;
11
use Exception;
12
use InvalidArgumentException;
13
use IteratorAggregate;
14
use Psr\Http\Message\ResponseInterface;
15
16
use Yiisoft\Http\Header;
17
18
use function array_keys;
19
use function array_values;
20
use function array_walk;
21
use function count;
22
use function in_array;
23
24
/**
25
 * A CookieCollection helps to work with many cookies at once and to read / modify response cookies.
26
 *
27
 * @see Cookie
28
 */
29
final class CookieCollection implements IteratorAggregate, ArrayAccess, Countable
30
{
31
    /**
32
     * @var Cookie[] the cookies in this collection (indexed by the cookie name)
33
     */
34
    private array $cookies = [];
35
36
    /**
37
     * CookieCollection constructor.
38
     *
39
     * @param Cookie[] $cookies the cookies that this collection initially contains.
40
     */
41 26
    public function __construct(array $cookies = [])
42
    {
43 26
        foreach ($cookies as $cookie) {
44 5
            if (!($cookie instanceof Cookie)) {
45 1
                throw new InvalidArgumentException('CookieCollection can contain only Cookie instances.');
46
            }
47
48 5
            $this->cookies[$cookie->getName()] = $cookie;
49
        }
50 26
    }
51
52
    /**
53
     * Returns the collection as a PHP array.
54
     * The array keys are cookie names, and the array values are the corresponding cookie objects.
55
     *
56
     * @return Cookie[]
57
     */
58 1
    public function toArray(): array
59
    {
60 1
        return $this->cookies;
61
    }
62
63
    /**
64
     * Returns an iterator for traversing the cookies in the collection.
65
     * This method is required by the SPL interface [[\IteratorAggregate]].
66
     * It will be implicitly called when you use `foreach` to traverse the collection.
67
     *
68
     * @return ArrayIterator
69
     */
70
    public function getIterator(): ArrayIterator
71
    {
72
        return new ArrayIterator($this->cookies);
73
    }
74
75
    /**
76
     * Returns whether there is a cookie with the specified name.
77
     * This method is required by the SPL interface [[\ArrayAccess]].
78
     * It is implicitly called when you use something like `isset($collection[$name])`.
79
     * This is equivalent to [[has()]].
80
     *
81
     * @param string $name the cookie name
82
     * @return bool whether the named cookie exists
83
     */
84 1
    public function offsetExists($name): bool
85
    {
86 1
        return $this->has($name);
87
    }
88
89
    /**
90
     * Returns the cookie with the specified name.
91
     * This method is required by the SPL interface [[\ArrayAccess]].
92
     * It is implicitly called when you use something like `$cookie = $collection[$name];`.
93
     * This is equivalent to [[get()]].
94
     *
95
     * @param string $name the cookie name
96
     * @return Cookie the cookie with the specified name, null if the named cookie does not exist.
97
     */
98 2
    public function offsetGet($name): Cookie
99
    {
100 2
        return $this->get($name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get($name) could return the type null which is incompatible with the type-hinted return Yiisoft\Yii\Web\Cookie. Consider adding an additional type-check to rule them out.
Loading history...
101
    }
102
103
    /**
104
     * Adds the cookie to the collection.
105
     * This method is required by the SPL interface [[\ArrayAccess]].
106
     * It is implicitly called when you use something like `$collection[$name] = $cookie;`.
107
     * This is equivalent to [[add()]].
108
     *
109
     * @param string $name the cookie name
110
     * @param Cookie $cookie the cookie to be added
111
     */
112 3
    public function offsetSet($name, $cookie): void
113
    {
114 3
        $this->add($cookie);
115 3
    }
116
117
    /**
118
     * Removes the named cookie.
119
     * This method is required by the SPL interface [[\ArrayAccess]].
120
     * It is implicitly called when you use something like `unset($collection[$name])`.
121
     * This is equivalent to [[remove()]].
122
     *
123
     * @param string $name the cookie name
124
     */
125 1
    public function offsetUnset($name): void
126
    {
127 1
        $this->remove($name);
128 1
    }
129
130
    /**
131
     * Returns the number of cookies in the collection.
132
     * This method is required by the SPL `Countable` interface.
133
     * It will be implicitly called when you use `count($collection)`.
134
     *
135
     * @return int the number of cookies in the collection.
136
     */
137 7
    public function count(): int
138
    {
139 7
        return count($this->cookies);
140
    }
141
142
    /**
143
     * Returns the cookie with the specified name.
144
     *
145
     * @param string $name the cookie name
146
     * @return Cookie|null the cookie with the specified name. Null if the named cookie does not exist.
147
     * @see getValue()
148
     */
149 5
    public function get(string $name): ?Cookie
150
    {
151 5
        return $this->cookies[$name] ?? null;
152
    }
153
154
    /**
155
     * Returns the value of the named cookie.
156
     *
157
     * @param string $name the cookie name
158
     * @param mixed $defaultValue the value that should be returned when the named cookie does not exist.
159
     * @return string|null the value of the named cookie or the default value if cookie is not set.
160
     * @see get()
161
     */
162 1
    public function getValue(string $name, $defaultValue = null): ?string
163
    {
164 1
        return isset($this->cookies[$name]) ? $this->cookies[$name]->getValue() : $defaultValue;
165
    }
166
167
    /**
168
     * Adds a cookie to the collection.
169
     * If there is already a cookie with the same name in the collection, it will be removed first.
170
     *
171
     * @param Cookie $cookie the cookie to be added
172
     */
173 15
    public function add(Cookie $cookie): void
174
    {
175 15
        $this->cookies[$cookie->getName()] = $cookie;
176 15
    }
177
178
    /**
179
     * Returns whether there is a cookie with the specified name.
180
     *
181
     * @param string $name the cookie name
182
     * @return bool whether the named cookie exists
183
     * @see remove()
184
     */
185 2
    public function has(string $name): bool
186
    {
187 2
        return isset($this->cookies[$name]);
188
    }
189
190
    /**
191
     * Removes a cookie.
192
     *
193
     * @param string $name the name of the cookie to be removed.
194
     * @return Cookie|null cookie that is removed
195
     */
196 3
    public function remove(string $name): ?Cookie
197
    {
198 3
        if (!isset($this->cookies[$name])) {
199 2
            return null;
200
        }
201
202 2
        $removed = $this->cookies[$name];
203 2
        unset($this->cookies[$name]);
204
205 2
        return $removed;
206
    }
207
208
    /**
209
     * Removes all cookies.
210
     */
211 1
    public function clear(): void
212
    {
213 1
        $this->cookies = [];
214 1
    }
215
216
    /**
217
     * Returns whether the collection already contains the cookie.
218
     *
219
     * @param Cookie $cookie the cookie to check for
220
     * @return bool whether cookie exists
221
     * @see has()
222
     */
223 1
    public function contains(Cookie $cookie): bool
224
    {
225 1
        return in_array($cookie, $this->cookies, true);
226
    }
227
228
    /**
229
     * Tests for the existence of the cookie that satisfies the given predicate.
230
     *
231
     * @param Closure $p The predicate.
232
     * @return bool whether the predicate is true for at least on cookie.
233
     */
234 1
    public function exists(Closure $p): bool
235
    {
236 1
        foreach ($this->cookies as $name => $cookie) {
237 1
            if ($p($cookie, $name)) {
238 1
                return true;
239
            }
240
        }
241
242 1
        return false;
243
    }
244
245
    /**
246
     * Expire the cookie with the specified name
247
     *
248
     * @param string $name the cookie name
249
     */
250 1
    public function expire(string $name): void
251
    {
252 1
        if (!isset($this->cookies[$name])) {
253
            return;
254
        }
255
256 1
        $this->cookies[$name] = $this->cookies[$name]->expire();
257 1
    }
258
259
    /**
260
     * Apply user supplied function to every cookie in the collection.
261
     * If you want to modify the cookie in the collection, specify the first
262
     * parameter of Closure as reference.
263
     *
264
     * @param Closure $p
265
     */
266 1
    public function walk(Closure $p): void
267
    {
268 1
        array_walk($this->cookies, $p);
269 1
    }
270
271
    /**
272
     * Gets all keys/indices of the collection.
273
     *
274
     * @return string[] The keys/indices of the collection.
275
     */
276 1
    public function getKeys(): array
277
    {
278 1
        return array_keys($this->cookies);
279
    }
280
281
    /**
282
     * Gets all cookies of the collection as an indexed array.
283
     *
284
     * @return Cookie[] The cookies in the collection, in the order they appear in the collection.
285
     */
286 1
    public function getValues(): array
287
    {
288 1
        return array_values($this->cookies);
289
    }
290
291
    /**
292
     * Checks whether the collection is empty (contains no cookies).
293
     *
294
     * @return bool whether the collection is empty.
295
     */
296 1
    public function isEmpty(): bool
297
    {
298 1
        return empty($this->cookies);
299
    }
300
301
    /**
302
     * Populates the cookie collection from an array of 'name' => 'value' pairs.
303
     *
304
     * @param array $array the cookies to populate from
305
     * @return static collection created from array
306
     */
307 5
    public static function fromArray(array $array): self
308
    {
309 5
        if (empty($array)) {
310 1
            return new self();
311
        }
312
313
        // check if associative array with 'name' => 'value' pairs is passed
314 4
        if (count(array_filter(array_keys($array), 'is_string')) !== count($array)) {
315 3
            throw new InvalidArgumentException('Array in wrong format is passed.');
316
        }
317
318 1
        return new self(array_map(static fn ($name, $value) => new Cookie($name, $value), array_keys($array), $array));
319
    }
320
321
    /**
322
     * Adds the cookies in the collection to response and returns it.
323
     *
324
     * @param ResponseInterface $response
325
     * @return ResponseInterface response with added cookies.
326
     */
327 2
    public function addToResponse(ResponseInterface $response): ResponseInterface
328
    {
329 2
        foreach ($this->cookies as $cookie) {
330 2
            $response = $cookie->addToResponse($response);
331
        }
332
333 2
        return $response;
334
    }
335
336
    /**
337
     * Creates a copy of the response with cookies set from the collection.
338
     *
339
     * @param ResponseInterface $response
340
     * @return ResponseInterface response with new cookies.
341
     */
342 1
    public function setToResponse(ResponseInterface $response): ResponseInterface
343
    {
344 1
        $response = $response->withoutHeader(Header::SET_COOKIE);
345 1
        return $this->addToResponse($response);
346
    }
347
348
    /**
349
     * Populates the cookie collection from a ResponseInterface.
350
     *
351
     * @param ResponseInterface $response the response object to populate from
352
     * @return static collection created from response
353
     * @throws Exception
354
     */
355 1
    public static function fromResponse(ResponseInterface $response): self
356
    {
357 1
        $collection = new self();
358 1
        foreach ($response->getHeader(Header::SET_COOKIE) as $setCookieString) {
359 1
            $cookie = Cookie::fromCookieString($setCookieString);
360 1
            $collection->add($cookie);
361
        }
362 1
        return $collection;
363
    }
364
}
365