Completed
Push — master ( 14b1fe...69643f )
by Mark
04:01
created

CollectionTrait::newCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         3.0.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Collection;
16
17
use AppendIterator;
18
use ArrayIterator;
19
use Cake\Collection\Iterator\BufferedIterator;
20
use Cake\Collection\Iterator\ExtractIterator;
21
use Cake\Collection\Iterator\FilterIterator;
22
use Cake\Collection\Iterator\InsertIterator;
23
use Cake\Collection\Iterator\MapReduce;
24
use Cake\Collection\Iterator\NestIterator;
25
use Cake\Collection\Iterator\ReplaceIterator;
26
use Cake\Collection\Iterator\SortIterator;
27
use Cake\Collection\Iterator\StoppableIterator;
28
use Cake\Collection\Iterator\TreeIterator;
29
use Cake\Collection\Iterator\UnfoldIterator;
30
use Cake\Collection\Iterator\ZipIterator;
31
use Countable;
32
use LimitIterator;
33
use LogicException;
34
use RecursiveIteratorIterator;
35
use Traversable;
36
37
/**
38
 * Offers a handful of methods to manipulate iterators
39
 */
40
trait CollectionTrait
41
{
42
43
    use ExtractTrait;
44
45
    /**
46
     * Returns a new collection.
47
     *
48
     * Allows classes which use this trait to determine their own
49
     * type of returned collection interface
50
     *
51
     * @param mixed ...$args Constructor arguments.
52
     * @return \Cake\Collection\CollectionInterface
53
     */
54
    protected function newCollection(...$args)
55
    {
56
        return new Collection(...$args);
57
    }
58
59
    /**
60
     * {@inheritDoc}
61
     */
62
    public function each(callable $c)
63
    {
64
        foreach ($this->optimizeUnwrap() as $k => $v) {
65
            $c($v, $k);
66
        }
67
68
        return $this;
69
    }
70
71
    /**
72
     * {@inheritDoc}
73
     *
74
     * @return \Cake\Collection\Iterator\FilterIterator
75
     */
76
    public function filter(callable $c = null)
77
    {
78
        if ($c === null) {
79
            $c = function ($v) {
80
                return (bool)$v;
81
            };
82
        }
83
84
        return new FilterIterator($this->unwrap(), $c);
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     *
90
     * @return \Cake\Collection\Iterator\FilterIterator
91
     */
92
    public function reject(callable $c)
93
    {
94
        return new FilterIterator($this->unwrap(), function ($key, $value, $items) use ($c) {
95
            return !$c($key, $value, $items);
96
        });
97
    }
98
99
    /**
100
     * {@inheritDoc}
101
     */
102 View Code Duplication
    public function every(callable $c)
103
    {
104
        foreach ($this->optimizeUnwrap() as $key => $value) {
105
            if (!$c($value, $key)) {
106
                return false;
107
            }
108
        }
109
110
        return true;
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 View Code Duplication
    public function some(callable $c)
117
    {
118
        foreach ($this->optimizeUnwrap() as $key => $value) {
119
            if ($c($value, $key) === true) {
120
                return true;
121
            }
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * {@inheritDoc}
129
     */
130
    public function contains($value)
131
    {
132
        foreach ($this->optimizeUnwrap() as $v) {
133
            if ($value === $v) {
134
                return true;
135
            }
136
        }
137
138
        return false;
139
    }
140
141
    /**
142
     * {@inheritDoc}
143
     *
144
     * @return \Cake\Collection\Iterator\ReplaceIterator
145
     */
146
    public function map(callable $c)
147
    {
148
        return new ReplaceIterator($this->unwrap(), $c);
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154
    public function reduce(callable $c, $zero = null)
155
    {
156
        $isFirst = false;
157
        if (func_num_args() < 2) {
158
            $isFirst = true;
159
        }
160
161
        $result = $zero;
162
        foreach ($this->optimizeUnwrap() as $k => $value) {
163
            if ($isFirst) {
164
                $result = $value;
165
                $isFirst = false;
166
                continue;
167
            }
168
            $result = $c($result, $value, $k);
169
        }
170
171
        return $result;
172
    }
173
174
    /**
175
     * {@inheritDoc}
176
     */
177
    public function extract($matcher)
178
    {
179
        $extractor = new ExtractIterator($this->unwrap(), $matcher);
180
        if (is_string($matcher) && strpos($matcher, '{*}') !== false) {
181
            $extractor = $extractor
182
                ->filter(function ($data) {
183
                    return $data !== null && ($data instanceof Traversable || is_array($data));
184
                })
185
                ->unfold();
186
        }
187
188
        return $extractor;
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194
    public function max($callback, $type = \SORT_NUMERIC)
195
    {
196
        return (new SortIterator($this->unwrap(), $callback, \SORT_DESC, $type))->first();
197
    }
198
199
    /**
200
     * {@inheritDoc}
201
     */
202
    public function min($callback, $type = \SORT_NUMERIC)
203
    {
204
        return (new SortIterator($this->unwrap(), $callback, \SORT_ASC, $type))->first();
205
    }
206
207
    /**
208
     * {@inheritDoc}
209
     */
210
    public function avg($matcher = null)
211
    {
212
        $result = $this;
213
        if ($matcher != null) {
214
            $result = $result->extract($matcher);
215
        }
216
        $result = $result
217
            ->reduce(function ($acc, $current) {
218
                list($count, $sum) = $acc;
219
220
                return [$count + 1, $sum + $current];
221
            }, [0, 0]);
222
223
        if ($result[0] === 0) {
224
            return null;
225
        }
226
227
        return $result[1] / $result[0];
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233
    public function median($matcher = null)
234
    {
235
        $elements = $this;
236
        if ($matcher != null) {
237
            $elements = $elements->extract($matcher);
238
        }
239
        $values = $elements->toList();
240
        sort($values);
241
        $count = count($values);
242
243
        if ($count === 0) {
244
            return null;
245
        }
246
247
        $middle = (int)($count / 2);
248
249
        if ($count % 2) {
250
            return $values[$middle];
251
        }
252
253
        return ($values[$middle - 1] + $values[$middle]) / 2;
254
    }
255
256
    /**
257
     * {@inheritDoc}
258
     */
259
    public function sortBy($callback, $dir = \SORT_DESC, $type = \SORT_NUMERIC)
260
    {
261
        return new SortIterator($this->unwrap(), $callback, $dir, $type);
262
    }
263
264
    /**
265
     * {@inheritDoc}
266
     */
267 View Code Duplication
    public function groupBy($callback)
268
    {
269
        $callback = $this->_propertyExtractor($callback);
270
        $group = [];
271
        foreach ($this->optimizeUnwrap() as $value) {
272
            $group[$callback($value)][] = $value;
273
        }
274
275
        return $this->newCollection($group);
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281 View Code Duplication
    public function indexBy($callback)
282
    {
283
        $callback = $this->_propertyExtractor($callback);
284
        $group = [];
285
        foreach ($this->optimizeUnwrap() as $value) {
286
            $group[$callback($value)] = $value;
287
        }
288
289
        return $this->newCollection($group);
290
    }
291
292
    /**
293
     * {@inheritDoc}
294
     */
295
    public function countBy($callback)
296
    {
297
        $callback = $this->_propertyExtractor($callback);
298
299
        $mapper = function ($value, $key, $mr) use ($callback) {
300
            /** @var \Cake\Collection\Iterator\MapReduce $mr */
301
            $mr->emitIntermediate($value, $callback($value));
302
        };
303
304
        $reducer = function ($values, $key, $mr) {
305
            /** @var \Cake\Collection\Iterator\MapReduce $mr */
306
            $mr->emit(count($values), $key);
307
        };
308
309
        return $this->newCollection(new MapReduce($this->unwrap(), $mapper, $reducer));
310
    }
311
312
    /**
313
     * {@inheritDoc}
314
     */
315
    public function sumOf($matcher = null)
316
    {
317
        if ($matcher === null) {
318
            return array_sum($this->toList());
319
        }
320
321
        $callback = $this->_propertyExtractor($matcher);
322
        $sum = 0;
323
        foreach ($this->optimizeUnwrap() as $k => $v) {
324
            $sum += $callback($v, $k);
325
        }
326
327
        return $sum;
328
    }
329
330
    /**
331
     * {@inheritDoc}
332
     */
333
    public function shuffle()
334
    {
335
        $elements = $this->toArray();
336
        shuffle($elements);
337
338
        return $this->newCollection($elements);
339
    }
340
341
    /**
342
     * {@inheritDoc}
343
     */
344
    public function sample($size = 10)
345
    {
346
        return $this->newCollection(new LimitIterator($this->shuffle(), 0, $size));
347
    }
348
349
    /**
350
     * {@inheritDoc}
351
     */
352
    public function take($size = 1, $from = 0)
353
    {
354
        return $this->newCollection(new LimitIterator($this, $from, $size));
355
    }
356
357
    /**
358
     * {@inheritDoc}
359
     */
360
    public function skip($howMany)
361
    {
362
        return $this->newCollection(new LimitIterator($this, $howMany));
363
    }
364
365
    /**
366
     * {@inheritDoc}
367
     */
368
    public function match(array $conditions)
369
    {
370
        return $this->filter($this->_createMatcherFilter($conditions));
371
    }
372
373
    /**
374
     * {@inheritDoc}
375
     */
376
    public function firstMatch(array $conditions)
377
    {
378
        return $this->match($conditions)->first();
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     */
384
    public function first()
385
    {
386
        $iterator = new LimitIterator($this, 0, 1);
387
        foreach ($iterator as $result) {
388
            return $result;
389
        }
390
    }
391
392
    /**
393
     * {@inheritDoc}
394
     */
395
    public function last()
396
    {
397
        $iterator = $this->optimizeUnwrap();
398
        if (is_array($iterator)) {
399
            return array_pop($iterator);
400
        }
401
402
        if ($iterator instanceof Countable) {
403
            $count = count($iterator);
404
            if ($count === 0) {
405
                return null;
406
            }
407
            $iterator = new LimitIterator($iterator, $count - 1, 1);
408
        }
409
410
        $result = null;
411
        foreach ($iterator as $result) {
412
            // No-op
413
        }
414
415
        return $result;
416
    }
417
418
    /**
419
     * {@inheritDoc}
420
     */
421
    public function takeLast($howMany)
422
    {
423
        if ($howMany < 1) {
424
            throw new \InvalidArgumentException("The takeLast method requires a number greater than 0.");
425
        }
426
427
        $iterator = $this->optimizeUnwrap();
428
        if (is_array($iterator)) {
429
            return $this->newCollection(array_slice($iterator, $howMany * -1));
430
        }
431
432
        if ($iterator instanceof Countable) {
433
            $count = count($iterator);
434
435
            if ($count === 0) {
436
                return $this->newCollection([]);
437
            }
438
439
            $iterator = new LimitIterator($iterator, max(0, $count - $howMany), $howMany);
440
441
            return $this->newCollection($iterator);
442
        }
443
444
        $generator = function ($iterator, $howMany) {
445
            $result = [];
446
            $bucket = 0;
447
            $offset = 0;
448
449
            /**
450
             * Consider the collection of elements [1, 2, 3, 4, 5, 6, 7, 8, 9], in order
451
             * to get the last 4 elements, we can keep a buffer of 4 elements and
452
             * fill it circularly using modulo logic, we use the $bucket variable
453
             * to track the position to fill next in the buffer. This how the buffer
454
             * looks like after 4 iterations:
455
             *
456
             * 0) 1 2 3 4 -- $bucket now goes back to 0, we have filled 4 elementes
457
             * 1) 5 2 3 4 -- 5th iteration
458
             * 2) 5 6 3 4 -- 6th iteration
459
             * 3) 5 6 7 4 -- 7th iteration
460
             * 4) 5 6 7 8 -- 8th iteration
461
             * 5) 9 6 7 8
462
             *
463
             *  We can see that at the end of the iterations, the buffer contains all
464
             *  the last four elements, just in the wrong order. How do we keep the
465
             *  original order? Well, it turns out that the number of iteration also
466
             *  give us a clue on what's going on, Let's add a marker for it now:
467
             *
468
             * 0) 1 2 3 4
469
             *    ^ -- The 0) above now becomes the $offset variable
470
             * 1) 5 2 3 4
471
             *      ^ -- $offset = 1
472
             * 2) 5 6 3 4
473
             *        ^ -- $offset = 2
474
             * 3) 5 6 7 4
475
             *          ^ -- $offset = 3
476
             * 4) 5 6 7 8
477
             *    ^  -- We use module logic for $offset too
478
             *          and as you can see each time $offset is 0, then the buffer
479
             *          is sorted exactly as we need.
480
             * 5) 9 6 7 8
481
             *      ^ -- $offset = 1
482
             *
483
             * The $offset variable is a marker for splitting the buffer in two,
484
             * elements to the right for the marker are the head of the final result,
485
             * whereas the elements at the left are the tail. For example consider step 5)
486
             * which has an offset of 1:
487
             *
488
             * - $head = elements to the right = [6, 7, 8]
489
             * - $tail = elements to the left =  [9]
490
             * - $result = $head + $tail = [6, 7, 8, 9]
491
             *
492
             * The logic above applies to collections of any size.
493
             */
494
495
            foreach ($iterator as $k => $item) {
496
                $result[$bucket] = [$k, $item];
497
                $bucket = (++$bucket) % $howMany;
498
                $offset++;
499
            }
500
501
            $offset = $offset % $howMany;
502
            $head = array_slice($result, $offset);
503
            $tail = array_slice($result, 0, $offset);
504
505
            foreach ($head as $v) {
506
                yield $v[0] => $v[1];
507
            }
508
509
            foreach ($tail as $v) {
510
                yield $v[0] => $v[1];
511
            }
512
        };
513
514
        return $this->newCollection($generator($iterator, $howMany));
515
    }
516
517
    /**
518
     * {@inheritDoc}
519
     */
520
    public function append($items)
521
    {
522
        $list = new AppendIterator();
523
        $list->append($this->unwrap());
524
        $list->append($this->newCollection($items)->unwrap());
525
526
        return $this->newCollection($list);
527
    }
528
529
    /**
530
     * {@inheritDoc}
531
     */
532
    public function appendItem($item, $key = null)
533
    {
534 View Code Duplication
        if ($key !== null) {
535
            $data = [$key => $item];
536
        } else {
537
            $data = [$item];
538
        }
539
540
        return $this->append($data);
541
    }
542
543
    /**
544
     * {@inheritDoc}
545
     */
546
    public function prepend($items)
547
    {
548
        return $this->newCollection($items)->append($this);
549
    }
550
551
    /**
552
     * {@inheritDoc}
553
     */
554
    public function prependItem($item, $key = null)
555
    {
556 View Code Duplication
        if ($key !== null) {
557
            $data = [$key => $item];
558
        } else {
559
            $data = [$item];
560
        }
561
562
        return $this->prepend($data);
563
    }
564
565
    /**
566
     * {@inheritDoc}
567
     */
568
    public function combine($keyPath, $valuePath, $groupPath = null)
569
    {
570
        $options = [
571
            'keyPath' => $this->_propertyExtractor($keyPath),
572
            'valuePath' => $this->_propertyExtractor($valuePath),
573
            'groupPath' => $groupPath ? $this->_propertyExtractor($groupPath) : null
574
        ];
575
576
        $mapper = function ($value, $key, $mapReduce) use ($options) {
577
            /** @var \Cake\Collection\Iterator\MapReduce $mapReduce */
578
            $rowKey = $options['keyPath'];
579
            $rowVal = $options['valuePath'];
580
581
            if (!$options['groupPath']) {
582
                $mapReduce->emit($rowVal($value, $key), $rowKey($value, $key));
583
584
                return null;
585
            }
586
587
            $key = $options['groupPath']($value, $key);
588
            $mapReduce->emitIntermediate(
589
                [$rowKey($value, $key) => $rowVal($value, $key)],
590
                $key
591
            );
592
        };
593
594
        $reducer = function ($values, $key, $mapReduce) {
595
            $result = [];
596
            foreach ($values as $value) {
597
                $result += $value;
598
            }
599
            /** @var \Cake\Collection\Iterator\MapReduce $mapReduce */
600
            $mapReduce->emit($result, $key);
601
        };
602
603
        return $this->newCollection(new MapReduce($this->unwrap(), $mapper, $reducer));
604
    }
605
606
    /**
607
     * {@inheritDoc}
608
     */
609
    public function nest($idPath, $parentPath, $nestingKey = 'children')
610
    {
611
        $parents = [];
612
        $idPath = $this->_propertyExtractor($idPath);
613
        $parentPath = $this->_propertyExtractor($parentPath);
614
        $isObject = true;
615
616
        $mapper = function ($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath, $nestingKey) {
617
            $row[$nestingKey] = [];
618
            $id = $idPath($row, $key);
619
            $parentId = $parentPath($row, $key);
620
            $parents[$id] =& $row;
621
            /** @var \Cake\Collection\Iterator\MapReduce $mapReduce */
622
            $mapReduce->emitIntermediate($id, $parentId);
623
        };
624
625
        $reducer = function ($values, $key, $mapReduce) use (&$parents, &$isObject, $nestingKey) {
626
            static $foundOutType = false;
627
            if (!$foundOutType) {
628
                $isObject = is_object(current($parents));
629
                $foundOutType = true;
630
            }
631
            if (empty($key) || !isset($parents[$key])) {
632
                foreach ($values as $id) {
633
                    $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1);
634
                    /** @var \Cake\Collection\Iterator\MapReduce $mapReduce */
635
                    $mapReduce->emit($parents[$id]);
636
                }
637
638
                return null;
639
            }
640
641
            $children = [];
642
            foreach ($values as $id) {
643
                $children[] =& $parents[$id];
644
            }
645
            $parents[$key][$nestingKey] = $children;
646
        };
647
648
        return $this->newCollection(new MapReduce($this->unwrap(), $mapper, $reducer))
649
            ->map(function ($value) use (&$isObject) {
650
                /** @var \ArrayIterator $value */
651
                return $isObject ? $value : $value->getArrayCopy();
652
            });
653
    }
654
655
    /**
656
     * {@inheritDoc}
657
     *
658
     * @return \Cake\Collection\Iterator\InsertIterator
659
     */
660
    public function insert($path, $values)
661
    {
662
        return new InsertIterator($this->unwrap(), $path, $values);
663
    }
664
665
    /**
666
     * {@inheritDoc}
667
     */
668
    public function toArray($preserveKeys = true)
669
    {
670
        $iterator = $this->unwrap();
671
        if ($iterator instanceof ArrayIterator) {
672
            $items = $iterator->getArrayCopy();
673
674
            return $preserveKeys ? $items : array_values($items);
675
        }
676
        // RecursiveIteratorIterator can return duplicate key values causing
677
        // data loss when converted into an array
678
        if ($preserveKeys && get_class($iterator) === 'RecursiveIteratorIterator') {
679
            $preserveKeys = false;
680
        }
681
682
        return iterator_to_array($this, $preserveKeys);
683
    }
684
685
    /**
686
     * {@inheritDoc}
687
     */
688
    public function toList()
689
    {
690
        return $this->toArray(false);
691
    }
692
693
    /**
694
     * {@inheritDoc}
695
     */
696
    public function jsonSerialize()
697
    {
698
        return $this->toArray();
699
    }
700
701
    /**
702
     * {@inheritDoc}
703
     */
704
    public function compile($preserveKeys = true)
705
    {
706
        return $this->newCollection($this->toArray($preserveKeys));
707
    }
708
709
    /**
710
     * {@inheritDoc}
711
     */
712
    public function lazy()
713
    {
714
        $generator = function () {
715
            foreach ($this->unwrap() as $k => $v) {
716
                yield $k => $v;
717
            }
718
        };
719
720
        return $this->newCollection($generator());
721
    }
722
723
    /**
724
     * {@inheritDoc}
725
     *
726
     * @return \Cake\Collection\Iterator\BufferedIterator
727
     */
728
    public function buffered()
729
    {
730
        return new BufferedIterator($this->unwrap());
731
    }
732
733
    /**
734
     * {@inheritDoc}
735
     *
736
     * @return \Cake\Collection\Iterator\TreeIterator
737
     */
738
    public function listNested($dir = 'desc', $nestingKey = 'children')
739
    {
740
        $dir = strtolower($dir);
741
        $modes = [
742
            'desc' => TreeIterator::SELF_FIRST,
743
            'asc' => TreeIterator::CHILD_FIRST,
744
            'leaves' => TreeIterator::LEAVES_ONLY
745
        ];
746
747
        return new TreeIterator(
748
            new NestIterator($this, $nestingKey),
749
            isset($modes[$dir]) ? $modes[$dir] : $dir
750
        );
751
    }
752
753
    /**
754
     * {@inheritDoc}
755
     *
756
     * @return \Cake\Collection\Iterator\StoppableIterator
757
     */
758
    public function stopWhen($condition)
759
    {
760
        if (!is_callable($condition)) {
761
            $condition = $this->_createMatcherFilter($condition);
762
        }
763
764
        return new StoppableIterator($this->unwrap(), $condition);
765
    }
766
767
    /**
768
     * {@inheritDoc}
769
     */
770
    public function unfold(callable $transformer = null)
771
    {
772
        if ($transformer === null) {
773
            $transformer = function ($item) {
774
                return $item;
775
            };
776
        }
777
778
        return $this->newCollection(
779
            new RecursiveIteratorIterator(
780
                new UnfoldIterator($this->unwrap(), $transformer),
781
                RecursiveIteratorIterator::LEAVES_ONLY
782
            )
783
        );
784
    }
785
786
    /**
787
     * {@inheritDoc}
788
     */
789
    public function through(callable $handler)
790
    {
791
        $result = $handler($this);
792
793
        return $result instanceof CollectionInterface ? $result : $this->newCollection($result);
794
    }
795
796
    /**
797
     * {@inheritDoc}
798
     */
799
    public function zip($items)
800
    {
801
        return new ZipIterator(array_merge([$this->unwrap()], func_get_args()));
802
    }
803
804
    /**
805
     * {@inheritDoc}
806
     */
807
    public function zipWith($items, $callable)
808
    {
809
        if (func_num_args() > 2) {
810
            $items = func_get_args();
811
            $callable = array_pop($items);
812
        } else {
813
            $items = [$items];
814
        }
815
816
        return new ZipIterator(array_merge([$this->unwrap()], $items), $callable);
817
    }
818
819
    /**
820
     * {@inheritDoc}
821
     */
822
    public function chunk($chunkSize)
823
    {
824
        return $this->map(function ($v, $k, $iterator) use ($chunkSize) {
825
            $values = [$v];
826
            for ($i = 1; $i < $chunkSize; $i++) {
827
                $iterator->next();
828
                if (!$iterator->valid()) {
829
                    break;
830
                }
831
                $values[] = $iterator->current();
832
            }
833
834
            return $values;
835
        });
836
    }
837
838
    /**
839
     * {@inheritDoc}
840
     */
841
    public function chunkWithKeys($chunkSize, $preserveKeys = true)
842
    {
843
        return $this->map(function ($v, $k, $iterator) use ($chunkSize, $preserveKeys) {
844
            $key = 0;
845
            if ($preserveKeys) {
846
                $key = $k;
847
            }
848
            $values = [$key => $v];
849
            for ($i = 1; $i < $chunkSize; $i++) {
850
                $iterator->next();
851
                if (!$iterator->valid()) {
852
                    break;
853
                }
854
                if ($preserveKeys) {
855
                    $values[$iterator->key()] = $iterator->current();
856
                } else {
857
                    $values[] = $iterator->current();
858
                }
859
            }
860
861
            return $values;
862
        });
863
    }
864
865
    /**
866
     * {@inheritDoc}
867
     */
868
    public function isEmpty()
869
    {
870
        foreach ($this as $el) {
871
            return false;
872
        }
873
874
        return true;
875
    }
876
877
    /**
878
     * {@inheritDoc}
879
     */
880
    public function unwrap()
881
    {
882
        $iterator = $this;
883
        while (get_class($iterator) === 'Cake\Collection\Collection') {
884
            $iterator = $iterator->getInnerIterator();
885
        }
886
887
        if ($iterator !== $this && $iterator instanceof CollectionInterface) {
888
            $iterator = $iterator->unwrap();
889
        }
890
891
        return $iterator;
892
    }
893
894
    /**
895
     * Backwards compatible wrapper for unwrap()
896
     *
897
     * @return \Traversable
898
     * @deprecated 3.0.10 Will be removed in 4.0.0
899
     */
900
    // @codingStandardsIgnoreLine
901
    public function _unwrap()
902
    {
903
        deprecationWarning('CollectionTrait::_unwrap() is deprecated. Use CollectionTrait::unwrap() instead.');
904
905
        return $this->unwrap();
906
    }
907
908
    /**
909
     * @param callable|null $operation Operation
910
     * @param callable|null $filter Filter
911
     * @return \Cake\Collection\CollectionInterface
912
     * @throws \LogicException
913
     */
914
    public function cartesianProduct(callable $operation = null, callable $filter = null)
915
    {
916
        if ($this->isEmpty()) {
917
            return $this->newCollection([]);
918
        }
919
920
        $collectionArrays = [];
921
        $collectionArraysKeys = [];
922
        $collectionArraysCounts = [];
923
924
        foreach ($this->toList() as $value) {
925
            $valueCount = count($value);
926
            if ($valueCount !== count($value, COUNT_RECURSIVE)) {
927
                throw new LogicException('Cannot find the cartesian product of a multidimensional array');
928
            }
929
930
            $collectionArraysKeys[] = array_keys($value);
931
            $collectionArraysCounts[] = $valueCount;
932
            $collectionArrays[] = $value;
933
        }
934
935
        $result = [];
936
        $lastIndex = count($collectionArrays) - 1;
937
        // holds the indexes of the arrays that generate the current combination
938
        $currentIndexes = array_fill(0, $lastIndex + 1, 0);
939
940
        $changeIndex = $lastIndex;
941
942
        while (!($changeIndex === 0 && $currentIndexes[0] === $collectionArraysCounts[0])) {
943
            $currentCombination = array_map(function ($value, $keys, $index) {
944
                return $value[$keys[$index]];
945
            }, $collectionArrays, $collectionArraysKeys, $currentIndexes);
946
947
            if ($filter === null || $filter($currentCombination)) {
948
                $result[] = ($operation === null) ? $currentCombination : $operation($currentCombination);
949
            }
950
951
            $currentIndexes[$lastIndex]++;
952
953
            for ($changeIndex = $lastIndex; $currentIndexes[$changeIndex] === $collectionArraysCounts[$changeIndex] && $changeIndex > 0; $changeIndex--) {
954
                $currentIndexes[$changeIndex] = 0;
955
                $currentIndexes[$changeIndex - 1]++;
956
            }
957
        }
958
959
        return $this->newCollection($result);
960
    }
961
962
    /**
963
     * {@inheritDoc}
964
     *
965
     * @return \Cake\Collection\CollectionInterface
966
     * @throws \LogicException
967
     */
968
    public function transpose()
969
    {
970
        $arrayValue = $this->toList();
971
        $length = count(current($arrayValue));
972
        $result = [];
973
        foreach ($arrayValue as $column => $row) {
974
            if (count($row) != $length) {
975
                throw new LogicException('Child arrays do not have even length');
976
            }
977
        }
978
979
        for ($column = 0; $column < $length; $column++) {
980
            $result[] = array_column($arrayValue, $column);
981
        }
982
983
        return $this->newCollection($result);
984
    }
985
986
    /**
987
     * {@inheritDoc}
988
     *
989
     * @return int
990
     */
991 View Code Duplication
    public function count()
992
    {
993
        $traversable = $this->optimizeUnwrap();
994
995
        if (is_array($traversable)) {
996
            return count($traversable);
997
        }
998
999
        return iterator_count($traversable);
1000
    }
1001
1002
    /**
1003
     * {@inheritDoc}
1004
     *
1005
     * @return int
1006
     */
1007
    public function countKeys()
1008
    {
1009
        return count($this->toArray());
1010
    }
1011
1012
    /**
1013
     * Unwraps this iterator and returns the simplest
1014
     * traversable that can be used for getting the data out
1015
     *
1016
     * @return \Traversable|array
1017
     */
1018
    protected function optimizeUnwrap()
1019
    {
1020
        $iterator = $this->unwrap();
1021
1022
        if (get_class($iterator) === ArrayIterator::class) {
1023
            $iterator = $iterator->getArrayCopy();
1024
        }
1025
1026
        return $iterator;
1027
    }
1028
}
1029