From 72cd437a010cb875de9288b7ac619c0a361803b9 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 13:27:20 +0300 Subject: [PATCH 01/10] add RecursiveIteratorAggregateIterator --- src/RecursiveIteratorAggregateIterator.php | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/RecursiveIteratorAggregateIterator.php diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php new file mode 100644 index 0000000..a5b5481 --- /dev/null +++ b/src/RecursiveIteratorAggregateIterator.php @@ -0,0 +1,39 @@ +stack[] = $input instanceof \IteratorAggregate ? $input->getIterator() : $input; + } + + public function getDepth() + { + return count($this->stack)-1; + } + + public function getIterator() : Generator + { + while (true) { + while (!empty($this->stack) && !end($this->stack)->valid()) { + array_pop($this->stack); + } + if (empty($this->stack)) return; + $current = end($this->stack)->current(); + yield $current; + end($this->stack)->next(); + $this->stack[] = $current instanceof \IteratorAggregate ? $current->getIterator() : $current; + } + } + +} From fc365d4b781d53a90c0d626341f67adaf8f09c13 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 14:01:58 +0300 Subject: [PATCH 02/10] add test, run cs --- src/RecursiveIteratorAggregateIterator.php | 19 +++--- ...RecursiveIteratorAggregateIteratorTest.php | 61 +++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 tests/unit/RecursiveIteratorAggregateIteratorTest.php diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index a5b5481..89e1462 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -5,8 +5,10 @@ namespace loophp\iterators; use Generator; -use Traversable; use IteratorAggregate; +use Traversable; + +use function count; class RecursiveIteratorAggregateIterator implements IteratorAggregate { @@ -14,26 +16,29 @@ class RecursiveIteratorAggregateIterator implements IteratorAggregate public function __construct(Traversable $input) { - $this->stack[] = $input instanceof \IteratorAggregate ? $input->getIterator() : $input; + $this->stack[] = $input instanceof IteratorAggregate ? $input->getIterator() : $input; } public function getDepth() { - return count($this->stack)-1; + return count($this->stack) - 1; } - public function getIterator() : Generator + public function getIterator(): Generator { while (true) { while (!empty($this->stack) && !end($this->stack)->valid()) { array_pop($this->stack); } - if (empty($this->stack)) return; + + if (empty($this->stack)) { + return; + } $current = end($this->stack)->current(); + yield $current; end($this->stack)->next(); - $this->stack[] = $current instanceof \IteratorAggregate ? $current->getIterator() : $current; + $this->stack[] = $current instanceof IteratorAggregate ? $current->getIterator() : $current; } } - } diff --git a/tests/unit/RecursiveIteratorAggregateIteratorTest.php b/tests/unit/RecursiveIteratorAggregateIteratorTest.php new file mode 100644 index 0000000..64ec589 --- /dev/null +++ b/tests/unit/RecursiveIteratorAggregateIteratorTest.php @@ -0,0 +1,61 @@ +items); + } +} + +/** + * @internal + * + * @coversNothing + */ +final class RecursiveIteratorAggregateIteratorTest extends TestCase +{ + public function testRecursiveIteratorAggregateIterator() + { + $input = [ + new RecursiveIteratorAggregateIteratorTestObject('1', [ + new RecursiveIteratorAggregateIteratorTestObject('1-1', [ + new RecursiveIteratorAggregateIteratorTestObject('1-1-1', []), + ]), + new RecursiveIteratorAggregateIteratorTestObject('1-2', []), + ]), + new RecursiveIteratorAggregateIteratorTestObject('2', []), + new RecursiveIteratorAggregateIteratorTestObject('3', [ + new RecursiveIteratorAggregateIteratorTestObject('3-1', []), + ]), + ]; + $expected = [ + '0: 1', + '1: 1-1', + '2: 1-1-1', + '1: 1-2', + '0: 2', + '0: 3', + '1: 3-1', + ]; + $result = []; + $iterator = new RecursiveIteratorAggregateIterator(new ArrayIterator($input)); + + foreach ($iterator as $item) { + $result[] = $iterator->getDepth() . ': ' . $item->name; + } + self::assertSame($expected, $result); + } +} From 96e59fd702ac53cb4161a18637e93bc0b61b8594 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 17:01:13 +0300 Subject: [PATCH 03/10] fix static analyse --- src/RecursiveIteratorAggregateIterator.php | 46 +++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index 89e1462..90156cd 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -5,40 +5,76 @@ namespace loophp\iterators; use Generator; +use Iterator; use IteratorAggregate; +use RuntimeException; use Traversable; use function count; +/** + * @template TKey + * @template T + * + * @implements IteratorAggregate + */ class RecursiveIteratorAggregateIterator implements IteratorAggregate { + /** + * @var list + */ private array $stack = []; + /** + * @param Traversable $input + */ public function __construct(Traversable $input) { - $this->stack[] = $input instanceof IteratorAggregate ? $input->getIterator() : $input; + $this->stack[] = self::findIterator($input); } - public function getDepth() + public function getDepth(): int { return count($this->stack) - 1; } + /** + * @return Generator + */ public function getIterator(): Generator { while (true) { - while (!empty($this->stack) && !end($this->stack)->valid()) { + while (count($this->stack) > 0 && !end($this->stack)->valid()) { array_pop($this->stack); } - if (empty($this->stack)) { + if (count($this->stack) === 0) { return; } $current = end($this->stack)->current(); yield $current; end($this->stack)->next(); - $this->stack[] = $current instanceof IteratorAggregate ? $current->getIterator() : $current; + $this->stack[] = self::findIterator($current); } } + + /** + * @phpstan-ignore missingType.iterableValue + */ + private static function findIterator(Traversable $input): Iterator + { + $prev = null; + + while (!($input instanceof Iterator)) { + if ($prev === $input || !($input instanceof IteratorAggregate)) { + throw new RuntimeException('Invalid iterator'); + } + $prev = $input; + $input = $input->getIterator(); + } + + // @phpstan-ignore return.type + return $input; + } } From c7c7395155e0d4fca10c06d2704e600cf9edaeda Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 17:26:09 +0300 Subject: [PATCH 04/10] move phpstan-ignore to baseline --- src/RecursiveIteratorAggregateIterator.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index 90156cd..29adcc6 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -59,9 +59,6 @@ public function getIterator(): Generator } } - /** - * @phpstan-ignore missingType.iterableValue - */ private static function findIterator(Traversable $input): Iterator { $prev = null; @@ -74,7 +71,6 @@ private static function findIterator(Traversable $input): Iterator $input = $input->getIterator(); } - // @phpstan-ignore return.type return $input; } } From def3d70e13dcbbcfa0788823572adbc7eae1304e Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 17:32:52 +0300 Subject: [PATCH 05/10] updated phpstan-baseline --- phpstan-baseline.neon | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 638ef6d..c6e501d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,16 +1,25 @@ parameters: - ignoreErrors: - - - message: "#^While loop condition is always true\\.$#" - count: 1 - path: src/RandomIntegerAggregate.php + ignoreErrors: + - + message: "#^While loop condition is always true\\.$#" + count: 1 + path: src/RandomIntegerAggregate.php - - - message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{TKey, T\\}\\>, loophp\\\\iterators\\\\UnpackIterableAggregate\\ given\\.$#" - count: 1 - path: src/RandomIterableAggregate.php + - + message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{TKey, T\\}\\>, loophp\\\\iterators\\\\UnpackIterableAggregate\\ given\\.$#" + count: 1 + path: src/RandomIterableAggregate.php - - - message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{array\\{TKey, T\\}\\|int\\|null, array\\{TKey, T\\}\\|int\\|null\\}\\>, loophp\\\\iterators\\\\SortIterableAggregate\\, array\\\\> given\\.$#" - count: 1 - path: src/RandomIterableAggregate.php + - + message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{array\\{TKey, T\\}\\|int\\|null, array\\{TKey, T\\}\\|int\\|null\\}\\>, loophp\\\\iterators\\\\SortIterableAggregate\\, array\\\\> given\\.$#" + count: 1 + path: src/RandomIterableAggregate.php + - + message: "#^Method loophp\\\\iterators\\\\RecursiveIteratorAggregateIterator\\:\\:findIterator\\(\\) has parameter \\$input with no value type specified in iterable type Traversable\\.$#" + count: 1 + path: src/RecursiveIteratorAggregateIterator.php + + - + message: "#^Method loophp\\\\iterators\\\\RecursiveIteratorAggregateIterator\\:\\:findIterator\\(\\) should return Iterator but returns Traversable\\.$#" + count: 1 + path: src/RecursiveIteratorAggregateIterator.php From bc965cd8b84c42bea44bc31ef431fa2873ea5ba1 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 17:56:37 +0300 Subject: [PATCH 06/10] RecursiveIteratorAggregateIterator move stack initialisation from constructor --- src/RecursiveIteratorAggregateIterator.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index 29adcc6..c32a9d7 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -28,10 +28,7 @@ class RecursiveIteratorAggregateIterator implements IteratorAggregate /** * @param Traversable $input */ - public function __construct(Traversable $input) - { - $this->stack[] = self::findIterator($input); - } + public function __construct(private readonly Traversable $input) {} public function getDepth(): int { @@ -43,6 +40,8 @@ public function getDepth(): int */ public function getIterator(): Generator { + $this->stack = [self::findIterator($this->input)]; + while (true) { while (count($this->stack) > 0 && !end($this->stack)->valid()) { array_pop($this->stack); From b84701f6f34e97bb2d5bf3b2fbdf84f8cdaf8139 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 18:29:14 +0300 Subject: [PATCH 07/10] Update src/RecursiveIteratorAggregateIterator.php Co-authored-by: Pol Dellaiera --- src/RecursiveIteratorAggregateIterator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index c32a9d7..7368e0d 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -47,7 +47,7 @@ public function getIterator(): Generator array_pop($this->stack); } - if (count($this->stack) === 0) { + if ($this->stack === []) { return; } $current = end($this->stack)->current(); From 1c8c30ee4adf4eff480ab0c4ab3533bee27135c6 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 19:39:40 +0300 Subject: [PATCH 08/10] optimise RecursiveIteratorAggregateIterator --- src/RecursiveIteratorAggregateIterator.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index c32a9d7..27aadab 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -32,7 +32,7 @@ public function __construct(private readonly Traversable $input) {} public function getDepth(): int { - return count($this->stack) - 1; + return count($this->stack); } /** @@ -40,21 +40,26 @@ public function getDepth(): int */ public function getIterator(): Generator { - $this->stack = [self::findIterator($this->input)]; + $iterator = self::findIterator($this->input); + $this->stack = []; while (true) { - while (count($this->stack) > 0 && !end($this->stack)->valid()) { - array_pop($this->stack); + while (null !== $iterator && $iterator->valid() === false) { + $iterator = array_pop($this->stack); } - if (count($this->stack) === 0) { + if (null === $iterator) { return; } - $current = end($this->stack)->current(); + $current = $iterator->current(); yield $current; - end($this->stack)->next(); - $this->stack[] = self::findIterator($current); + $iterator->next(); + + if ($current instanceof Traversable) { + $this->stack[] = $iterator; + $iterator = self::findIterator($current); + } } } From 7960a7110365ee542e5f9c04766722818d6f05c6 Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 19:40:59 +0300 Subject: [PATCH 09/10] RecursiveIteratorAggregateIterator make findIterator not static --- src/RecursiveIteratorAggregateIterator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index 27aadab..02bdda1 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -40,7 +40,7 @@ public function getDepth(): int */ public function getIterator(): Generator { - $iterator = self::findIterator($this->input); + $iterator = $this->findIterator($this->input); $this->stack = []; while (true) { @@ -58,12 +58,12 @@ public function getIterator(): Generator if ($current instanceof Traversable) { $this->stack[] = $iterator; - $iterator = self::findIterator($current); + $iterator = $this->findIterator($current); } } } - private static function findIterator(Traversable $input): Iterator + private function findIterator(Traversable $input): Iterator { $prev = null; From 36cd094649cdc7bd3e0d4c2fc13a736d81b2c59f Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 26 Jul 2024 20:57:59 +0300 Subject: [PATCH 10/10] add exception on secondary iteration start --- src/RecursiveIteratorAggregateIterator.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/RecursiveIteratorAggregateIterator.php b/src/RecursiveIteratorAggregateIterator.php index 02bdda1..75bc958 100644 --- a/src/RecursiveIteratorAggregateIterator.php +++ b/src/RecursiveIteratorAggregateIterator.php @@ -23,7 +23,7 @@ class RecursiveIteratorAggregateIterator implements IteratorAggregate /** * @var list */ - private array $stack = []; + private ?array $stack = null; /** * @param Traversable $input @@ -32,7 +32,7 @@ public function __construct(private readonly Traversable $input) {} public function getDepth(): int { - return count($this->stack); + return null === $this->stack ? -1 : count($this->stack); } /** @@ -40,8 +40,11 @@ public function getDepth(): int */ public function getIterator(): Generator { - $iterator = $this->findIterator($this->input); + if (null !== $this->stack) { + throw new RuntimeException('Iterator already active '); + } $this->stack = []; + $iterator = $this->findIterator($this->input); while (true) { while (null !== $iterator && $iterator->valid() === false) { @@ -49,6 +52,8 @@ public function getIterator(): Generator } if (null === $iterator) { + $this->stack = null; + return; } $current = $iterator->current();