Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce class ipl\Stdlib\CallbackFilterIterator #54

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/CallbackFilterIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace ipl\Stdlib;

use CallbackFilterIterator as SplCallbackFilterIterator;

/**
* CallbackFilterIterator that behaves like generators in terms of validity before the first call to `rewind()`
*
* 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.
*/
class CallbackFilterIterator extends SplCallbackFilterIterator
{
/** @var bool Whether iteration has started */
private $started = false;

public function rewind(): void
{
$this->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()

Check failure on line 48 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.2 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::key() is not compatible with return type mixed of method FilterIterator::key().

Check failure on line 48 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.3 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::key() is not compatible with return type mixed of method FilterIterator::key().

Check failure on line 48 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.4 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::key() is not covariant with return type mixed of method FilterIterator::key().
{
if ($this->started) {
return parent::key();
}

$this->rewind();

return parent::key();
}

#[\ReturnTypeWillChange]
public function current()

Check failure on line 60 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.2 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::current() is not compatible with return type mixed of method FilterIterator::current().

Check failure on line 60 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.3 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::current() is not compatible with return type mixed of method FilterIterator::current().

Check failure on line 60 in src/CallbackFilterIterator.php

View workflow job for this annotation

GitHub Actions / Static analysis for php 7.4 on ubuntu-latest

Return type mixed of method ipl\Stdlib\CallbackFilterIterator::current() is not covariant with return type mixed of method FilterIterator::current().
{
if ($this->started) {
return parent::current();
}

$this->rewind();

return parent::current();
}
}
70 changes: 70 additions & 0 deletions tests/CallbackFilterIteratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace ipl\Tests\Stdlib;

use ipl\Stdlib\CallbackFilterIterator;

class CallbackFilterIteratorTest extends TestCase
{
public function testFirstIterationStep()
{
$iterator = new CallbackFilterIterator(new \ArrayIterator([1, 2, 3]), function (int $i) {
return $i === 2;
});

foreach ($iterator as $k => $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());
}
}
Loading