From 6e0498c4b307977666455c972a7c113169b1614c Mon Sep 17 00:00:00 2001 From: Andrew Moyes Date: Wed, 11 Oct 2023 21:02:19 +0200 Subject: [PATCH] Add a tap function (#95) This function lazily performs a side effect for each item in an iterable, without changing the keys/values of said iterable. This is useful for things like logging, saving partial results to a database, or any other side effects which should not change the outcome of the full iteration pipeline. --- README.md | 1 + src/iter.php | 33 +++++++++++++++++++++++++++++++++ test/iterTest.php | 23 +++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/README.md b/README.md index 6b456ed..9e65794 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ list the function signatures as an overview: Iterator flip(iterable $iterable) Iterator chunk(iterable $iterable, int $size, bool $preserveKeys = false) Iterator chunkWithKeys(iterable $iterable, int $size) + Iterator tap(callable $function, iterable $iterable) Iterator toIter(iterable $iterable) Iterator range(number $start, number $end, number $step = null) diff --git a/src/iter.php b/src/iter.php index da760a5..bba487e 100644 --- a/src/iter.php +++ b/src/iter.php @@ -1257,6 +1257,39 @@ function toArrayWithKeys(iterable $iterable): array { function isIterable($value) { return is_array($value) || $value instanceof \Traversable; } + +/** + * Lazily performs a side effect for each value in an iterable. + * + * The passed function is called with the value and key of each element of the + * iterable. The return value of the function is ignored. + * + * Examples: + * $iterable = iter\range(1, 3); + * // => iterable(1, 2, 3) + * + * $iterable = iter\tap(function($value, $key) { echo $value; }, $iterable); + * // => iterable(1, 2, 3) : pending side effects + * + * iter\toArray($iterable); + * // => [1, 2, 3] + * // "123" : side effects were executed + * + * @template TKey + * @template TValue + * + * @param callable(TValue, TKey):void $function A function to call for each value as a side effect + * @param iterable $iterable The iterable to tap + * + * @return iterable + */ +function tap(callable $function, iterable $iterable): \Iterator { + foreach ($iterable as $key => $value) { + $function($value, $key); + yield $key => $value; + } +} + /* * Python: * compress() diff --git a/test/iterTest.php b/test/iterTest.php index 021cbba..11c9e70 100644 --- a/test/iterTest.php +++ b/test/iterTest.php @@ -618,6 +618,29 @@ function() { \TypeError::class ]; } + + public function testTap() { + # Simple case where callback does not return anything + $this->assertSame( + [1, 2, 3], + toArray(tap(function() {}, [1, 2, 3])) + ); + + # Should also not care about the return value of the callback + # Also, check that this is called once for every element in the iterable + $mock = $this->getMockBuilder(\stdClass::class) + ->setMethods(['foo']) + ->getMock(); + + $mock->expects($this->exactly(3)) + ->method('foo') + ->will($this->returnArgument(42)); + + $this->assertSame( + [1, 2, 3], + toArray(tap([$mock, 'foo'], [1, 2, 3])) + ); + } } class _CountableTestDummy implements \Countable {