Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ZipIteratorIterator
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
6 / 6
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 current
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 next
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 valid
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 rewind
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of php-fast-forward/iterators.
7 *
8 * This source file is subject to the license that is bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2025-2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/iterators
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Iterator;
20
21use Iterator;
22use InvalidArgumentException;
23
24/**
25 * Combines multiple iterators into a single iterator, returning arrays of grouped values.
26 *
27 * This iterator synchronously iterates over multiple traversable sources,
28 * yielding an array where each element corresponds to the current value of each source.
29 * The iteration stops when the **shortest** iterator is exhausted.
30 *
31 * ## Usage Example:
32 *
33 * @example Zipping two iterators
34 * ```php
35 * use FastForward\Iterator\ZipIteratorIterator;
36 * use ArrayIterator;
37 *
38 * $dataSetOne = new ArrayIterator([1, 2, 3]);
39 * $dataSetTwo = new ArrayIterator(['A', 'B', 'C']);
40 *
41 * $zippedIterator = new ZipIteratorIterator($dataSetOne, $dataSetTwo);
42 *
43 * foreach ($zippedIterator as $pair) {
44 *     print_r($pair);
45 * }
46 * // Outputs:
47 * // [1, 'A']
48 * // [2, 'B']
49 * // [3, 'C']
50 * ```
51 *
52 * **Note:** The iterator stops when the shortest iterable is exhausted.
53 *
54 * @since 1.0.0
55 */
56class ZipIteratorIterator extends CountableIterator
57{
58    /**
59     * @var array<int, Iterator> the list of active iterators
60     */
61    private  array $iterators;
62
63    /**
64     * @var int the current iteration index
65     */
66    private int $currentIndex = 0;
67
68    /**
69     * Initializes the ZipIteratorIterator.
70     *
71     * @param iterable ...$iterators The iterators to be combined.
72     *
73     * @throws InvalidArgumentException if fewer than two iterators are provided
74     */
75    public function __construct(iterable ...$iterators)
76    {
77        if (\count($iterators) < 2) {
78            throw new InvalidArgumentException('At least two iterators are required.');
79        }
80
81        $this->iterators = array_map(
82            static fn(iterable $iterator): Iterator => new IterableIterator($iterator),
83            $iterators
84        );
85
86        // Inicializa os iteradores
87        foreach ($this->iterators as $iterator) {
88            $iterator->rewind();
89        }
90    }
91
92    /**
93     * Retrieves the current set of values from each iterator.
94     *
95     * @return array<int, mixed> the array of current values from each iterator
96     */
97    public function current(): array
98    {
99        return array_map(static fn(Iterator $iterator): mixed => $iterator->current(), $this->iterators);
100    }
101
102    /**
103     * Retrieves the current key (index) of the iteration.
104     *
105     * @return int the current index
106     */
107    public function key(): int
108    {
109        return $this->currentIndex;
110    }
111
112    /**
113     * Moves to the next set of values in each iterator.
114     *
115     * @return void
116     */
117    public function next(): void
118    {
119        foreach ($this->iterators as $iterator) {
120            $iterator->next();
121        }
122
123        ++$this->currentIndex;
124    }
125
126    /**
127     * Checks if all iterators still have valid elements.
128     *
129     * The iteration stops when the shortest iterator is exhausted.
130     *
131     * @return bool true if valid elements exist, false otherwise
132     */
133    public function valid(): bool
134    {
135        foreach ($this->iterators as $iterator) {
136            if (! $iterator->valid()) {
137                return false;
138            }
139        }
140
141        return true;
142    }
143
144    /**
145     * Resets the iterator to the beginning.
146     *
147     * @return void
148     */
149    public function rewind(): void
150    {
151        $this->currentIndex = 0;
152
153        foreach ($this->iterators as $iterator) {
154            $iterator->rewind();
155        }
156    }
157}