Skip to content

Commit bda15b8

Browse files
committed
Add new iterators & add support to allow iterables on construction of iterators
1 parent a67cbfb commit bda15b8

20 files changed

+305
-48
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"source": "https://github.com/php-fast-forward/iterators"
2424
},
2525
"require": {
26-
"php": "^7.4 || ^8.0"
26+
"php": "^8.1"
2727
},
2828
"require-dev": {
2929
"coisa/php-cs-fixer": "^2.1",

examples/chain-iterator.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(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+
* @link https://github.com/php-fast-forward/iterators
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <[email protected]>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
require_once dirname(__DIR__) . '/vendor/autoload.php';
17+
18+
use FastForward\Iterator\ChainIterableIterator;
19+
20+
use function FastForward\Iterator\debugIterable;
21+
22+
/**
23+
* Sample dataset for testing InterleaveIteratorIterator.
24+
*
25+
* @var ArrayIterator<int, int> $data1
26+
* @var ArrayIterator<int, int> $data2
27+
* @var ArrayIterator<int, int> $data3
28+
*/
29+
$data1 = [1, 4, 7];
30+
$data2 = new ArrayIterator([2, 5, 8]);
31+
$data3 = new ArrayIterator([3, 6, 9]);
32+
33+
/**
34+
* Creates a ChainIterableIterator with iterables to chain.
35+
*
36+
* @var ChainIterableIterator<int> $chain
37+
*/
38+
$chain = new ChainIterableIterator($data1, $data2, $data3);
39+
40+
// Debugging the output of InterleaveIteratorIterator.
41+
debugIterable($chain, 'InterleaveIteratorIterator :: Interleaved Iteration');

examples/closure-iterator-iterator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919

2020
require_once dirname(__DIR__) . '/vendor/autoload.php';
2121

22+
/**
23+
* Sample dataset for testing ClosureIteratorIterator..
24+
*
25+
* @var array<int, int> $dataSet
26+
*/
2227
$data = [1, 2, 3, 4, 5];
2328

2429
/**
@@ -29,7 +34,7 @@
2934
* @return ClosureIteratorIterator<int, int> the transformed iterator
3035
*/
3136
$doubleIterator = new ClosureIteratorIterator(
32-
new ArrayIterator($data),
37+
$data,
3338
static fn (int $value): int => $value * 2
3439
);
3540

examples/consecutive-group-iterator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
/**
2323
* Sample dataset for testing ConsecutiveGroupIterator.
2424
*
25-
* @var ArrayIterator<int, int> $dataSet
25+
* @var array|ArrayIterator<int, int> $dataSet
2626
*/
27-
$dataSet = new ArrayIterator([1, 1, 2, 2, 2, 3, 4, 4, 5]);
27+
$dataSet = [1, 1, 2, 2, 2, 3, 4, 4, 5];
2828

2929
/**
3030
* Creates a ConsecutiveGroupIterator to group consecutive equal elements.

examples/interleave-iterator-iterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @var ArrayIterator<int, int> $data2
2727
* @var ArrayIterator<int, int> $data3
2828
*/
29-
$data1 = new ArrayIterator([1, 4, 7]);
29+
$data1 = [1, 4, 7];
3030
$data2 = new ArrayIterator([2, 5, 8]);
3131
$data3 = new ArrayIterator([3, 6, 9]);
3232

src/ChainIterableIterator.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
declare(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+
* @link https://github.com/php-fast-forward/iterators
12+
* @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <[email protected]>
13+
* @license https://opensource.org/licenses/MIT MIT License
14+
*/
15+
16+
namespace FastForward\Iterator;
17+
18+
/**
19+
* Class ChainIterableIterator.
20+
*
21+
* An iterator that chains multiple iterable sources together into a single unified iterator.
22+
*
23+
* This iterator SHALL accept any number of iterable values (arrays, Traversables, or Iterators)
24+
* and iterate over them in order. When the current iterator is exhausted, it proceeds to the next.
25+
*
26+
* The class MUST ensure all incoming values are wrapped as \Iterator instances, either natively
27+
* or by converting Traversables or arrays using standard SPL iterators.
28+
*
29+
* Example usage:
30+
*
31+
* ```php
32+
* $it = new ChainIterableIterator([1, 2], new ArrayIterator([3, 4]));
33+
* foreach ($it as $value) {
34+
* echo $value;
35+
* }
36+
* // Output: 1234
37+
* ```
38+
*
39+
* @package FastForward\Iterator
40+
*
41+
* @since 1.1.0
42+
*/
43+
final class ChainIterableIterator implements \Iterator
44+
{
45+
/**
46+
* @var \Iterator[] a list of iterators chained in sequence
47+
*/
48+
private array $iterators;
49+
50+
/**
51+
* @var int the index of the currently active iterator
52+
*/
53+
private int $currentIndex = 0;
54+
55+
/**
56+
* Constructs a ChainIterableIterator with one or more iterable sources.
57+
*
58+
* Each iterable SHALL be normalized to a \Iterator instance using:
59+
* - \ArrayIterator for arrays
60+
* - \IteratorIterator for Traversable objects
61+
* - Directly used if already an \Iterator
62+
*
63+
* @param iterable ...$iterables One or more iterable data sources to chain.
64+
*/
65+
public function __construct(iterable ...$iterables)
66+
{
67+
$this->iterators = array_map(
68+
static fn (iterable $iterable) => new IterableIterator($iterable),
69+
$iterables
70+
);
71+
}
72+
73+
/**
74+
* Rewinds all underlying iterators and resets the position.
75+
*
76+
* Each chained iterator SHALL be rewound to its beginning.
77+
*/
78+
public function rewind(): void
79+
{
80+
foreach ($this->iterators as $iterable) {
81+
$iterable->rewind();
82+
}
83+
84+
$this->currentIndex = 0;
85+
}
86+
87+
/**
88+
* Checks whether the current position is valid across chained iterators.
89+
*
90+
* Iteration continues until the current iterator is valid or all are exhausted.
91+
*
92+
* @return bool TRUE if there are more elements to iterate; FALSE otherwise
93+
*/
94+
public function valid(): bool
95+
{
96+
while (isset($this->iterators[$this->currentIndex])) {
97+
if ($this->iterators[$this->currentIndex]->valid()) {
98+
return true;
99+
}
100+
++$this->currentIndex;
101+
}
102+
103+
return false;
104+
}
105+
106+
/**
107+
* Returns the current element from the active iterator.
108+
*
109+
* @return null|mixed the current element or NULL if iteration is invalid
110+
*/
111+
public function current(): mixed
112+
{
113+
if (!$this->valid()) {
114+
return null;
115+
}
116+
117+
return $this->iterators[$this->currentIndex]->current();
118+
}
119+
120+
/**
121+
* Returns the current key from the active iterator.
122+
*
123+
* @return null|mixed the current key or NULL if iteration is invalid
124+
*/
125+
public function key(): mixed
126+
{
127+
if (!$this->valid()) {
128+
return null;
129+
}
130+
131+
return $this->iterators[$this->currentIndex]->key();
132+
}
133+
134+
/**
135+
* Moves the pointer of the active iterator forward.
136+
*/
137+
public function next(): void
138+
{
139+
if (!$this->valid()) {
140+
return;
141+
}
142+
143+
$this->iterators[$this->currentIndex]->next();
144+
}
145+
}

src/ClosureFactoryIteratorAggregate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,6 @@ public function __construct(\Closure $factory)
109109
*/
110110
public function getIterator(): \Traversable
111111
{
112-
return \call_user_func($this->factory);
112+
return new IterableIterator(\call_user_func($this->factory));
113113
}
114114
}

src/ClosureIteratorIterator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ class ClosureIteratorIterator extends \IteratorIterator
5757
/**
5858
* Initializes the ClosureIteratorIterator.
5959
*
60-
* @param \Traversable $iterator the underlying iterator to wrap
61-
* @param \Closure $closure the transformation function applied to each element
60+
* @param iterable $iterator the underlying iterator to wrap
61+
* @param \Closure $closure the transformation function applied to each element
6262
*/
63-
public function __construct(\Traversable $iterator, \Closure $closure)
63+
public function __construct(iterable $iterator, \Closure $closure)
6464
{
65-
parent::__construct($iterator);
65+
parent::__construct(new IterableIterator($iterator));
6666
$this->closure = $closure;
6767
}
6868

src/ConsecutiveGroupIterator.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ class ConsecutiveGroupIterator extends \IteratorIterator
6767
/**
6868
* Initializes the ConsecutiveGroupIterator.
6969
*
70-
* @param \Traversable $iterator the iterator containing values to be chunked
71-
* @param \Closure $callback The function that determines whether elements should be in the same chunk.
72-
* It receives two arguments: `$previous` and `$current`,
73-
* and must return `true` to keep them together or `false` to start a new chunk.
70+
* @param iterable $iterator the iterator containing values to be chunked
71+
* @param \Closure $callback The function that determines whether elements should be in the same chunk.
72+
* It receives two arguments: `$previous` and `$current`,
73+
* and must return `true` to keep them together or `false` to start a new chunk.
7474
*/
75-
public function __construct(\Traversable $iterator, \Closure $callback)
75+
public function __construct(iterable $iterator, \Closure $callback)
7676
{
77-
parent::__construct($iterator);
77+
parent::__construct(new IterableIterator($iterator));
7878
$this->callback = $callback;
7979
}
8080

src/FileExtensionFilterIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class FileExtensionFilterIterator extends \FilterIterator
5757
/**
5858
* Initializes the FileExtensionFilterIterator.
5959
*
60-
* @param \FilesystemIterator|\RecursiveDirectoryIterator $iterator The directory iterator to wrap.
60+
* @param \FilesystemIterator|\RecursiveDirectoryIterator $iterator the directory iterator to wrap
6161
* @param string ...$extensions The allowed file extensions.
6262
*/
6363
public function __construct(\FilesystemIterator $iterator, string ...$extensions)

0 commit comments

Comments
 (0)