Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
GeneratorRewindableIterator
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
6 / 6
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rewind
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 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%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 valid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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 IteratorAggregate;
22use IteratorIterator;
23use Closure;
24use Generator;
25
26/**
27 * An iterator that allows rewinding over a generator by caching its values.
28 *
29 * This class wraps a generator or a closure returning a generator, enabling
30 * multiple iterations over the generated sequence by caching its results.
31 *
32 * ## Usage Example:
33 *
34 * @example Using a Generator
35 * ```php
36 * use FastForward\Iterator\GeneratorRewindableIterator;
37 *
38 * $iterator = new GeneratorRewindableIterator(
39 *     (function () {
40 *         yield 'A';
41 *         yield 'B';
42 *         yield 'C';
43 *     })()
44 * );
45 *
46 * foreach ($iterator as $value) {
47 *     echo $value; // Outputs: A B C
48 * }
49 *
50 * foreach ($iterator as $value) {
51 *     echo $value; // Outputs: A B C (rewound)
52 * }
53 * ```
54 * @example Using a Callable that Returns a Generator
55 * ```php
56 * use FastForward\Iterator\GeneratorRewindableIterator;
57 *
58 * $iterator = new GeneratorRewindableIterator(fn () => (function () {
59 *     yield 1;
60 *     yield 2;
61 *     yield 3;
62 * })());
63 *
64 * foreach ($iterator as $value) {
65 *     echo $value; // Outputs: 1 2 3
66 * }
67 *
68 * foreach ($iterator as $value) {
69 *     echo $value; // Outputs: 1 2 3 (rewound)
70 * }
71 * ```
72 *
73 * **Note:** This implementation ensures that the generator can be rewound
74 * by caching its results using `GeneratorCachingIteratorAggregate`.
75 *
76 * @since 1.0.0
77 */
78class GeneratorRewindableIterator extends CountableIterator
79{
80    /**
81     * @var IteratorAggregate the iterator aggregate that caches generated values
82     */
83    private  IteratorAggregate $iteratorAggregate;
84
85    /**
86     * @var IteratorIterator the internal innerIterator iterator used for iteration
87     */
88    private IteratorIterator $innerIterator;
89
90    /**
91     * Initializes the GeneratorRewindableIterator with a generator or a closure returning a generator.
92     *
93     * @param Closure|Generator $generator a generator instance or a callable that returns a generator
94     */
95    public function __construct(Closure|Generator $generator)
96    {
97        $this->iteratorAggregate = new GeneratorCachingIteratorAggregate($generator);
98    }
99
100    /**
101     * Rewinds the iterator to the beginning.
102     *
103     * This method creates a new `IteratorIterator` instance wrapping the
104     * `GeneratorCachingIteratorAggregate`, ensuring that the generator can be reused.
105     *
106     * @return void
107     */
108    public function rewind(): void
109    {
110        $this->innerIterator = new IteratorIterator($this->iteratorAggregate->getIterator());
111        $this->innerIterator->rewind();
112    }
113
114    /**
115     * Retrieves the current element from the iterator.
116     *
117     * @return mixed the current element
118     */
119    public function current(): mixed
120    {
121        return $this->innerIterator->current();
122    }
123
124    /**
125     * Retrieves the key of the current element.
126     *
127     * @return mixed the key associated with the current element
128     */
129    public function key(): mixed
130    {
131        return $this->innerIterator->key();
132    }
133
134    /**
135     * Advances the iterator to the next element.
136     *
137     * @return void
138     */
139    public function next(): void
140    {
141        $this->innerIterator->next();
142    }
143
144    /**
145     * Checks if the current iterator position is valid.
146     *
147     * @return bool true if the current position is valid, false otherwise
148     */
149    public function valid(): bool
150    {
151        return $this->innerIterator->valid();
152    }
153}