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
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 |