diff --git a/README.md b/README.md index 61fd732..f944143 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ All entry points always return an instance of the pipeline. | `max()` | Finds the highest value. | `max` | | `min()` | Finds the lowest value. | `min` | | `count()` | Counts values. Eagerly executed.| `array_count` | +| `each()` | Eagerly iterates over the sequence. | `foreach`, `array_walk` | | `runningCount()` | Counts seen values using a reference argument. | | | `toArray()` | Returns an array with all values. Eagerly executed. | `dict`, `ToDictionary` | | `toArrayPreservingKeys()` | Returns an array with all values and keys. Eagerly executed. | | @@ -387,6 +388,18 @@ $result = $pipeline->toArray(); If in the example about one would use `iterator_to_array($result)` they would get just `[3, 4]`. +## `$pipeline->each()` + +Eagerly iterates over the sequence using the provided callback. + +```php +$pipeline->each(function ($i) { + $this->log("Saw $i"); +}); +``` + +Discards the sequence after iteration unless instructed otherwise by the second argument. + ## `$pipeline->getIterator()` A method to conform to the `Traversable` interface. In case of unprimed `\Pipeline\Standard` it'll return an empty array iterator, essentially a no-op pipeline. Therefore this should work without errors: diff --git a/src/Standard.php b/src/Standard.php index c194a41..69e15b2 100644 --- a/src/Standard.php +++ b/src/Standard.php @@ -1121,4 +1121,27 @@ public function finalVariance( return $variance; } + + /** + * Eagerly iterates over the sequence using the provided callback. Discards the sequence after iteration. + * + * @param callable $func + * @param bool $discard Whenever to discard the pipeline's interator. + */ + public function each(callable $func, bool $discard = true): void + { + if ($this->empty()) { + return; + } + + try { + foreach ($this->pipeline as $key => $value) { + $func($value, $key); + } + } finally { + if ($discard) { + $this->discard(); + } + } + } } diff --git a/tests/EachTest.php b/tests/EachTest.php new file mode 100644 index 0000000..dfa5cf9 --- /dev/null +++ b/tests/EachTest.php @@ -0,0 +1,154 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare(strict_types=1); + +namespace Tests\Pipeline; + +use LogicException; +use PHPUnit\Framework\TestCase; +use Pipeline\Standard; + +use ArrayIterator; + +use function Pipeline\map; +use function Pipeline\take; +use function Pipeline\fromArray; + +/** + * @covers \Pipeline\Standard + * + * @internal + */ +final class EachTest extends TestCase +{ + private array $output; + + protected function setUp(): void + { + parent::setUp(); + $this->output = []; + } + + protected function observeValue($value): void + { + $this->output[] = $value; + } + + protected function observeKeyValue($key, $value): void + { + $this->output[$key] = $value; + } + + public function testUninitialized(): void + { + $pipeline = new Standard(); + + $pipeline->each(fn ($value) => $this->observeValue($value)); + + $this->assertSame([], $this->output); + } + + public function testEmpty(): void + { + $pipeline = take([]); + + $pipeline->each(fn ($value) => $this->observeValue($value)); + + $this->assertSame([], $this->output); + } + + public function testEmptyGenerator(): void + { + $pipeline = map(static fn () => yield from []); + + $pipeline->each(fn ($value) => $this->observeValue($value)); + + $this->assertSame([], $this->output); + } + + public function testNotEmpty(): void + { + $pipeline = take([1, 2, 3, 4]); + + $pipeline->each(fn (int $value) => $this->observeValue($value)); + + $this->assertSame([1, 2, 3, 4], $this->output); + } + + public function testKeyValue(): void + { + $pipeline = take([5 => 1, 2, 3, 4]); + + $pipeline->each(fn (int $value, int $key) => $this->observeKeyValue($key, $value)); + + $this->assertSame([5 => 1, 2, 3, 4], $this->output); + } + + + public static function provideInterrupt(): iterable + { + yield [map(fn () => yield from [1, 2, 3, 4])]; + yield [take(new ArrayIterator([1, 2, 3, 4]))]; + } + + /** + * @dataProvider provideInterrupt + */ + public function testInterrupt(Standard $pipeline): void + { + $pipeline->cast(function (int $value) { + if (3 === $value) { + throw new LogicException(); + } + + return $value; + }); + + try { + $pipeline->each(function (int $value): void { + $this->observeValue($value); + }); + } catch (LogicException $_) { + $this->assertSame([1, 2], $this->output); + } + + $pipeline->each(function (int $value): void { + $this->fail(); + }); + + $this->assertSame([1, 2], $this->output); + $this->assertSame([], $pipeline->toArray()); + } + + public function testNoDiscard(): void + { + $pipeline = fromArray([1, 2, 3]); + + $pipeline->each(function (int $value): void { + $this->observeValue($value); + }, false); + + $pipeline->each(function (int $value): void { + $this->observeValue($value); + }); + + $this->assertSame([1, 2, 3, 1, 2, 3], $this->output); + $this->assertSame([], $pipeline->toArray()); + } + +}