This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /* |
||
3 | * This file is part of Dimsh\Models\Collections; |
||
4 | * (c) Abdulrahman Dimashki <[email protected]> |
||
5 | * |
||
6 | * For the full copyright and license information, please view the LICENSE |
||
7 | * file that was distributed with this source code. |
||
8 | */ |
||
9 | |||
10 | namespace Dimsh\Models\Collections; |
||
11 | |||
12 | /** |
||
13 | * Class Collection |
||
14 | * |
||
15 | * @method {} offsetGet(); |
||
16 | * @method {} @current() |
||
17 | * @method {} @last() |
||
18 | * @method {} @first() |
||
19 | * |
||
20 | * @package Dimsh\Models\Collections; |
||
21 | */ |
||
22 | class Collection implements \Iterator, \ArrayAccess, \Countable |
||
23 | { |
||
24 | /** |
||
25 | * @var array |
||
26 | */ |
||
27 | protected $list = []; |
||
28 | |||
29 | /** |
||
30 | * @var int |
||
31 | */ |
||
32 | protected $position = 0; |
||
33 | |||
34 | /** |
||
35 | * this is required for the unset() <=> offsetUnset() where an exception is |
||
36 | * thrown if next() or rewind() is not called after unset. |
||
37 | * @var bool |
||
38 | */ |
||
39 | protected $next_or_rewind_required = false; |
||
40 | |||
41 | 87 | public function __construct() |
|
42 | { |
||
43 | 87 | $this->position = 0; |
|
44 | 87 | } |
|
45 | |||
46 | /** |
||
47 | * @return int |
||
48 | */ |
||
49 | 12 | public function count() |
|
50 | { |
||
51 | 12 | return count($this->list); |
|
52 | } |
||
53 | |||
54 | /** |
||
55 | * This method is meant to be overridden by child Classes in order to throw |
||
56 | * an Exception if the value can't be added, else it will be added normally. |
||
57 | * |
||
58 | * @param mixed $value |
||
59 | */ |
||
60 | 66 | protected function preAdd($value) |
|
61 | { |
||
62 | 66 | } |
|
63 | |||
64 | /** |
||
65 | * Get the next numeric offset. |
||
66 | * |
||
67 | * we get the MAX numeric offset we have and increment it by 1. |
||
68 | */ |
||
69 | 57 | protected function nextOffset() |
|
70 | { |
||
71 | 57 | $keys = array_keys($this->list); |
|
72 | 57 | $keys = array_filter($keys, 'is_numeric'); // get numeric keys only |
|
73 | 57 | if ($keys) { |
|
0 ignored issues
–
show
|
|||
74 | 42 | $last_key = max($keys); |
|
75 | 14 | } else { |
|
76 | 57 | $last_key = -1; |
|
77 | } |
||
78 | 57 | return $last_key + 1; |
|
79 | } |
||
80 | |||
81 | /** |
||
82 | * @param string|int $offset |
||
83 | * @param mixed $value |
||
84 | * |
||
85 | * @throws \Exception |
||
86 | */ |
||
87 | 72 | public function offsetSet($offset, $value) |
|
88 | { |
||
89 | 72 | if ($offset === null) { |
|
90 | // a value is appended using [] array append operator. |
||
91 | 57 | $offset = $this->nextOffset(); |
|
92 | 19 | } |
|
93 | 72 | $this->preAdd($value); |
|
94 | 69 | $this->list[$offset] = &$value; |
|
95 | 69 | } |
|
96 | |||
97 | /** |
||
98 | * This is not in the interface, add an item. |
||
99 | * @return self |
||
100 | * @throws \Exception |
||
101 | */ |
||
102 | 12 | public function add($item) |
|
103 | { |
||
104 | 12 | $this->offsetSet(null, $item); |
|
105 | 12 | return $this; |
|
106 | } |
||
107 | |||
108 | /** |
||
109 | * This is not in the interface, add an item passing it by reference. |
||
110 | * |
||
111 | * This will only accept variables as input. |
||
112 | * |
||
113 | * @return self |
||
114 | * @throws \Exception |
||
115 | */ |
||
116 | 3 | public function addByReference(&$item) |
|
117 | { |
||
118 | 3 | $this->preAdd($item); |
|
119 | 3 | $this->list[$this->nextOffset()] = &$item; |
|
120 | 3 | return $this; |
|
121 | } |
||
122 | |||
123 | /** |
||
124 | * @param string|int $offset |
||
125 | * |
||
126 | * @return mixed|null |
||
127 | */ |
||
128 | 21 | public function &offsetGet($offset) |
|
129 | { |
||
130 | 21 | if (array_key_exists($offset, $this->list)) { |
|
131 | 18 | return $this->list[$offset]; |
|
132 | } |
||
133 | 3 | $null = null; |
|
134 | 3 | return $null; |
|
135 | } |
||
136 | |||
137 | /** |
||
138 | * @return bool |
||
139 | */ |
||
140 | 6 | public function offsetExists($offset) |
|
141 | { |
||
142 | 6 | return (array_key_exists($offset, $this->list)); |
|
143 | } |
||
144 | |||
145 | 12 | public function offsetUnset($offset) |
|
146 | { |
||
147 | 12 | $offset_pos = $this->offsetPosition($offset); |
|
148 | 12 | if ($offset_pos !== false && $this->position === $offset_pos) { |
|
149 | 12 | $this->next_or_rewind_required = true; |
|
150 | 12 | if ($this->position > 0) { |
|
151 | $this->position--; |
||
152 | } |
||
153 | 4 | } |
|
154 | 12 | unset($this->list[$offset]); |
|
155 | 12 | } |
|
156 | |||
157 | 18 | public function rewind() |
|
158 | { |
||
159 | 18 | $this->position = 0; |
|
160 | 18 | $this->next_or_rewind_required = false; |
|
161 | 18 | } |
|
162 | |||
163 | 9 | public function next() |
|
164 | { |
||
165 | 9 | ++$this->position; |
|
166 | 9 | $this->next_or_rewind_required = false; |
|
167 | 9 | } |
|
168 | |||
169 | /** |
||
170 | * This is not in the interface, go back in the array. |
||
171 | */ |
||
172 | 3 | public function prev() |
|
173 | { |
||
174 | 3 | --$this->position; |
|
175 | 3 | $this->next_or_rewind_required = false; |
|
176 | 3 | } |
|
177 | |||
178 | /** |
||
179 | * @return bool |
||
180 | */ |
||
181 | 12 | public function valid() |
|
182 | { |
||
183 | 12 | if (empty($this->list)) { |
|
184 | 6 | return false; |
|
185 | } |
||
186 | 6 | return array_key_exists($this->position, array_keys($this->list)); |
|
187 | } |
||
188 | |||
189 | 21 | public function current() |
|
190 | { |
||
191 | 21 | if ($this->next_or_rewind_required) { |
|
192 | 3 | throw new \Exception('Calling next or rewind is required after call to unset, current item can\'t be accessed otherwise because it is removed.'); |
|
193 | } |
||
194 | 18 | return $this->list[$this->key()]; |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * @return string |
||
199 | */ |
||
200 | 18 | public function key() |
|
201 | { |
||
202 | 18 | $keys = array_keys($this->list); |
|
203 | 18 | $offset = $keys[$this->position]; |
|
204 | 18 | return $offset; |
|
205 | } |
||
206 | |||
207 | /** |
||
208 | * @return int |
||
209 | */ |
||
210 | 3 | public function position() |
|
211 | { |
||
212 | 3 | return $this->position; |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * This is not in the interface, Get the last item. |
||
217 | */ |
||
218 | 6 | public function last() |
|
219 | { |
||
220 | 6 | $keys = array_keys($this->list); |
|
221 | 6 | $offset = array_pop($keys); |
|
222 | 6 | if (array_key_exists($offset, $this->list)) { |
|
223 | 3 | return $this->list[$offset]; |
|
224 | } |
||
225 | 3 | $null = null; |
|
226 | 3 | return $null; |
|
227 | } |
||
228 | |||
229 | /** |
||
230 | * This is not in the interface, Get the first item. |
||
231 | */ |
||
232 | 6 | public function first() |
|
233 | { |
||
234 | 6 | $keys = array_keys($this->list); |
|
235 | 6 | $offset = array_shift($keys); |
|
236 | 6 | if (array_key_exists($offset, $this->list)) { |
|
237 | 3 | return $this->list[$offset]; |
|
238 | } |
||
239 | 3 | $null = null; |
|
240 | 3 | return $null; |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * This is not in the interface, Return the keys of the internal list. |
||
245 | * |
||
246 | * @return array |
||
247 | */ |
||
248 | 9 | public function keys() |
|
249 | { |
||
250 | 9 | return array_keys($this->list); |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * This is not in the interface, cast the internal list to an array. |
||
255 | * |
||
256 | * @return array |
||
257 | */ |
||
258 | 24 | public function toArray() |
|
259 | { |
||
260 | 24 | return $this->list; |
|
261 | } |
||
262 | |||
263 | |||
264 | /** |
||
265 | * This is not in the interface, merge this object with another object and act |
||
266 | * like array_merge, numeric indexes are added, other indexes are overridden. |
||
267 | * |
||
268 | * This method will alter the current object. |
||
269 | * |
||
270 | * @param Collection $another_list |
||
271 | * |
||
272 | * @return self |
||
273 | * |
||
274 | * @throws \Exception |
||
275 | * |
||
276 | */ |
||
277 | 3 | public function merge(Collection $another_list) |
|
278 | { |
||
279 | 3 | for ($another_list->rewind(); $another_list->valid(); $another_list->next()) { |
|
280 | $key = $another_list->key(); |
||
281 | try { |
||
282 | $this->offsetSet(is_numeric($key) ? null : $key, $another_list->current()); |
||
283 | } catch (\Exception $e) { |
||
284 | throw $e; |
||
285 | } |
||
286 | } |
||
287 | 3 | return $this; |
|
288 | } |
||
289 | |||
290 | /** |
||
291 | * Change the position of item at $offset1 in the internal array to be at |
||
292 | * the position of item at $offset2 and vise versa. |
||
293 | * |
||
294 | * @param $offset1 |
||
295 | * @param $offset2 |
||
296 | * |
||
297 | * @return $this |
||
298 | * @throws \Exception |
||
299 | */ |
||
300 | 9 | public function swap($offset1, $offset2) |
|
301 | { |
||
302 | 9 | $pos1 = $this->offsetPosition($offset1); |
|
303 | 9 | $pos2 = $this->offsetPosition($offset2); |
|
304 | 9 | if ($pos1 === false || $pos2 === false) { |
|
305 | // offset does not exist |
||
306 | 3 | throw new \Exception("Trying to swap non exist offsets"); |
|
307 | } |
||
308 | 6 | if ($pos1 == $pos2) { |
|
309 | 3 | return $this; |
|
310 | } |
||
311 | 3 | $temp = $this[$offset1]; |
|
312 | 3 | $this[$offset1] = $this[$offset2]; |
|
313 | 3 | $this[$offset2] = $temp; |
|
314 | |||
315 | 3 | return $this; |
|
316 | } |
||
317 | |||
318 | /** |
||
319 | * Change the position of item at $offset1 in the internal array to be at |
||
320 | * the position of item at $offset2 and vise versa using re-order and refilling |
||
321 | * the array |
||
322 | * |
||
323 | * @param $offset1 |
||
324 | * @param $offset2 |
||
325 | * |
||
326 | * @return self |
||
327 | * |
||
328 | * @throws \Exception |
||
329 | */ |
||
330 | 9 | public function swapReorder($offset1, $offset2) |
|
331 | { |
||
332 | 9 | $pos1 = $this->offsetPosition($offset1); |
|
333 | 9 | $pos2 = $this->offsetPosition($offset2); |
|
334 | 9 | if ($pos1 === false || $pos2 === false) { |
|
335 | // offset does not exist |
||
336 | 3 | throw new \Exception("Trying to swap non exist offsets"); |
|
337 | } |
||
338 | 6 | if ($pos1 == $pos2) { |
|
339 | 3 | return $this; |
|
340 | } |
||
341 | |||
342 | 3 | $clone = clone $this; |
|
343 | 3 | for ($clone->rewind(); $clone->valid(); $clone->next()) { |
|
344 | 3 | $key = $clone->key(); |
|
345 | 3 | $curr_pos = $clone->position(); |
|
346 | 3 | if ($curr_pos === $pos1) { |
|
347 | 3 | $this[$key] = $clone->offsetGet($offset2); |
|
348 | 3 | } elseif ($curr_pos === $pos2) { |
|
349 | 3 | $this[$key] = $clone->offsetGet($offset1); |
|
350 | 1 | } else { |
|
351 | 3 | $this[$key] = $clone->current(); |
|
352 | } |
||
353 | 1 | } |
|
354 | |||
355 | 3 | return $this; |
|
356 | } |
||
357 | |||
358 | |||
359 | /** |
||
360 | * Get the item by internal pointer numeric position and not by the offset. |
||
361 | * |
||
362 | * This is not in the interface. |
||
363 | * |
||
364 | * @param int $pos |
||
365 | * |
||
366 | * @return null|mixed |
||
367 | */ |
||
368 | 6 | public function byPositionGet($pos) |
|
369 | { |
||
370 | 6 | $keys = $this->keys(); |
|
371 | 6 | if (array_key_exists($pos, $keys)) { |
|
372 | 3 | $offset = $keys[$pos]; |
|
373 | 3 | return $this->list[$offset]; |
|
374 | } |
||
375 | |||
376 | 3 | return null; |
|
377 | } |
||
378 | |||
379 | /** |
||
380 | * Get the internal pointer numeric position which corresponds to the |
||
381 | * offset passed. |
||
382 | * this is not in the interface |
||
383 | * |
||
384 | * @param $offset |
||
385 | * |
||
386 | * @return int|FALSE |
||
387 | */ |
||
388 | 27 | public function offsetPosition($offset) |
|
389 | { |
||
390 | 27 | $keys = array_keys($this->list); |
|
391 | 27 | $key = array_search($offset, $keys); |
|
392 | 27 | return $key; |
|
393 | } |
||
394 | |||
395 | /** |
||
396 | * Check if this container is empty. |
||
397 | * |
||
398 | * This is not in the interface |
||
399 | * @return bool |
||
400 | */ |
||
401 | 6 | public function isEmpty() |
|
402 | { |
||
403 | 6 | return count($this) === 0; |
|
404 | } |
||
405 | |||
406 | /** |
||
407 | * Perform array_diff_key against another list where the first parameter is |
||
408 | * this collection and the second is the new list. |
||
409 | * |
||
410 | * This will return a new Collection. |
||
411 | * |
||
412 | * @param Collection $another_list |
||
413 | * |
||
414 | * @return Collection |
||
415 | */ |
||
416 | 3 | public function diffKey(Collection $another_list) |
|
417 | { |
||
418 | 3 | $diff = array_diff_key($this->toArray(), $another_list->toArray()); |
|
419 | 3 | $new_list = new static(); |
|
420 | 3 | foreach ($diff as $key => $value) { |
|
421 | 3 | $new_list[$key] = $value; |
|
422 | 1 | } |
|
423 | 3 | return $new_list; |
|
424 | } |
||
425 | |||
426 | /** |
||
427 | * A version of php array_diff_assoc which is recursive. |
||
428 | * |
||
429 | * array_diff_assoc — Computes the difference of arrays with additional index check |
||
430 | * |
||
431 | * @param array $array1 |
||
432 | * @param array $array2 |
||
433 | * |
||
434 | * @return array |
||
435 | */ |
||
436 | 9 | protected static function arrayDiffRecursiveAssoc(array $array1, array $array2) |
|
437 | { |
||
438 | 9 | $ret = array(); |
|
439 | |||
440 | 9 | foreach ($array1 as $key1 => $value1) { |
|
441 | 9 | if (!array_key_exists($key1, $array2)) { |
|
442 | 6 | $ret[$key1] = $value1; |
|
443 | 6 | continue; |
|
444 | } |
||
445 | 3 | if (is_array($value1) && is_array($array2[$key1])) { |
|
446 | $recursive_diff = self::arrayDiffRecursiveAssoc($value1, $array2[$key1]); |
||
447 | if (count($recursive_diff)) { |
||
448 | $ret[$key1] = $recursive_diff; |
||
449 | } |
||
450 | 3 | } elseif ($value1 instanceof Collection && $array2[$key1] instanceof Collection) { |
|
451 | 3 | $recursive_diff = $value1->diffRecursiveAssoc($array2[$key1]); |
|
452 | 3 | if ($recursive_diff->count()) { |
|
453 | 2 | $ret[$key1] = $recursive_diff; |
|
454 | } |
||
455 | 3 | } elseif (!is_array($value1) && !is_array($array2[$key1])) { |
|
456 | 3 | if ($value1 !== $array2[$key1]) { |
|
457 | 2 | $ret[$key1] = $value1; |
|
458 | } |
||
459 | 1 | } else { |
|
460 | 1 | $ret[$key1] = $value1; |
|
461 | } |
||
462 | 3 | } |
|
463 | 9 | return $ret; |
|
464 | } |
||
465 | |||
466 | /** |
||
467 | * Return Recursive diff from another list with index check. |
||
468 | * |
||
469 | * The first array is this collection and compute the diff against the passed collection. |
||
470 | * |
||
471 | * This will return a new Collection. |
||
472 | * |
||
473 | * @param Collection $another_list |
||
474 | * |
||
475 | * @return static |
||
476 | */ |
||
477 | 9 | public function diffRecursiveAssoc(Collection $another_list) |
|
478 | { |
||
479 | 9 | $ret = new static(); |
|
480 | 9 | $diff = static::arrayDiffRecursiveAssoc($this->toArray(), $another_list->toArray()); |
|
481 | 9 | foreach ($diff as $key => $value) { |
|
482 | 6 | $ret[$key] = $value; |
|
483 | 3 | } |
|
484 | 9 | return $ret; |
|
485 | } |
||
486 | |||
487 | /** |
||
488 | * A version of php array_diff which is recursive. |
||
489 | * |
||
490 | * array_diff — Computes the difference of arrays |
||
491 | * |
||
492 | * This function is costy because it uses array_search |
||
493 | * |
||
494 | * @param array $array1 |
||
495 | * @param array $array2 |
||
496 | * |
||
497 | * @return array |
||
498 | */ |
||
499 | 3 | protected static function arrayDiffRecursive(array $array1, array $array2) |
|
500 | { |
||
501 | 3 | $ret = array(); |
|
502 | |||
503 | 3 | foreach ($array1 as $key1 => $value1) { |
|
504 | 3 | if (false === ($key2 = array_search($value1, $array2, true))) { |
|
505 | 3 | $ret[$key1] = $value1; |
|
506 | 3 | continue; |
|
507 | } |
||
508 | 3 | if (is_array($value1) && is_array($array2[$key2])) { |
|
509 | $recursive_diff = self::arrayDiffRecursive($value1, $array2[$key2]); |
||
510 | if (count($recursive_diff)) { |
||
511 | $ret[$key1] = $recursive_diff; |
||
512 | } |
||
513 | 3 | } elseif ($value1 instanceof Collection && $array2[$key2] instanceof Collection) { |
|
514 | $recursive_diff = $value1->diffRecursive($array2[$key2]); |
||
515 | if ($recursive_diff->count()) { |
||
516 | $ret[$key1] = $recursive_diff; |
||
517 | } |
||
518 | 3 | } elseif (!is_array($value1) && !is_array($array2[$key2])) { |
|
519 | 3 | if ($value1 !== $array2[$key2]) { |
|
520 | 2 | $ret[$key1] = $value1; |
|
521 | } |
||
522 | 1 | } else { |
|
523 | 1 | $ret[$key1] = $value1; |
|
524 | } |
||
525 | 1 | } |
|
526 | 3 | return $ret; |
|
527 | } |
||
528 | |||
529 | /** |
||
530 | * Return Recursive diff from another list. |
||
531 | * |
||
532 | * The first array is this collection and compute the diff against the passed collection. |
||
533 | * |
||
534 | * This will return a new Collection. |
||
535 | * |
||
536 | * @param Collection $another_list |
||
537 | * |
||
538 | * @return static |
||
539 | */ |
||
540 | 3 | public function diffRecursive(Collection $another_list) |
|
541 | { |
||
542 | 3 | $ret = new static(); |
|
543 | 3 | $diff = static::arrayDiffRecursive($this->toArray(), $another_list->toArray()); |
|
544 | 3 | foreach ($diff as $key => $value) { |
|
545 | 3 | $ret[$key] = $value; |
|
546 | 1 | } |
|
547 | 3 | return $ret; |
|
548 | } |
||
549 | |||
550 | /** |
||
551 | * Implementation of array_intesect_key against another list. |
||
552 | * |
||
553 | * This will return a new Collection. |
||
554 | * |
||
555 | * @param Collection $another_list |
||
556 | * |
||
557 | * @return static |
||
558 | */ |
||
559 | 3 | public function intersectKey(Collection $another_list) |
|
560 | { |
||
561 | 3 | $ret = new static(); |
|
562 | 3 | foreach ($this as $key => $value) { |
|
563 | 3 | if ($another_list->offsetExists($key)) { |
|
564 | 3 | $ret[$key] = $value; |
|
565 | 1 | } |
|
566 | 1 | } |
|
567 | 3 | return $ret; |
|
568 | } |
||
569 | } |
||
570 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.