From ed7cb0eb430e49f4bb5e18ed43e39419de041dac Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 26 Mar 2024 09:11:24 +0100 Subject: [PATCH] Introduce class `ipl\Stdlib\CallbackFilterIterator` This class stems from the motivation to have a filter iterator that uses callbacks, which behaves like any other iterator. Just like the SPL's `CallbackFilterIterator`, but that works differently: It isn't valid unless explicitly rewound first. Any other iterator, especially generators, doesn't need to be rewound first. That's most obvious with a generator that yields conditionally, just like a filter iterator would. Any call to `valid()`, `key()` or `current()` should return the same result as the very first iteration step would. --- src/CallbackFilterIterator.php | 70 ++++++++++++++++++++++++++++ tests/CallbackFilterIteratorTest.php | 70 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/CallbackFilterIterator.php create mode 100644 tests/CallbackFilterIteratorTest.php diff --git a/src/CallbackFilterIterator.php b/src/CallbackFilterIterator.php new file mode 100644 index 0000000..d119e05 --- /dev/null +++ b/src/CallbackFilterIterator.php @@ -0,0 +1,70 @@ +started = true; + + parent::rewind(); + } + + public function valid(): bool + { + if ($this->started) { + return parent::valid(); + } + + // As per php-src, \CallbackFilterIterator::rewind() forwards the iterator to the first valid element + // (https://github.com/php/php-src/blob/5cba2a3dc59ef2a0e432b05ab27f2b3ab4da48d0/ext/spl/spl_iterators.c#L1686) + $this->rewind(); + + return parent::valid(); + } + + #[\ReturnTypeWillChange] + public function key() + { + if ($this->started) { + return parent::key(); + } + + $this->rewind(); + + return parent::key(); + } + + #[\ReturnTypeWillChange] + public function current() + { + if ($this->started) { + return parent::current(); + } + + $this->rewind(); + + return parent::current(); + } +} diff --git a/tests/CallbackFilterIteratorTest.php b/tests/CallbackFilterIteratorTest.php new file mode 100644 index 0000000..0e67a03 --- /dev/null +++ b/tests/CallbackFilterIteratorTest.php @@ -0,0 +1,70 @@ + $v) { + $this->assertSame(1, $k); + $this->assertSame(2, $v); + } + } + + public function testValidBeforeRewind() + { + $iterator = new CallbackFilterIterator(new \ArrayIterator([1, 2, 3]), function (int $i) { + return $i === 2; + }); + + $this->assertTrue($iterator->valid()); + + $iterator->rewind(); + + $this->assertTrue($iterator->valid()); + } + + public function testKeyBeforeRewind() + { + $iterator = new CallbackFilterIterator(new \ArrayIterator([1, 2, 3]), function (int $i) { + return $i === 2; + }); + + $this->assertSame(1, $iterator->key()); + + $iterator->rewind(); + + $this->assertSame(1, $iterator->key()); + } + + public function testCurrentBeforeRewind() + { + $iterator = new CallbackFilterIterator(new \ArrayIterator([1, 2, 3]), function (int $i) { + return $i === 2; + }); + + $this->assertSame(2, $iterator->current()); + + $iterator->rewind(); + + $this->assertSame(2, $iterator->current()); + } + + public function testInvalidBeforeRewind() + { + $iterator = new CallbackFilterIterator(new \ArrayIterator([1, 2, 3]), function (int $i) { + return $i === 4; + }); + + $this->assertFalse($iterator->valid()); + $this->assertNull($iterator->key()); + $this->assertNull($iterator->current()); + } +}