Skip to content
Draft
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
44 changes: 30 additions & 14 deletions src/Standard.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Generator;
use Iterator;
use IteratorAggregate;
use ReflectionFunction;
use Traversable;
use Override;

Expand All @@ -49,6 +50,7 @@
use function mt_getrandmax;
use function mt_rand;
use function array_keys;
use function array_walk;

/**
* Concrete pipeline with sensible default callbacks.
Expand Down Expand Up @@ -1395,31 +1397,45 @@ public function each(callable $func, bool $discard = true): void
}
}

/**
* @param callable(TValue, TKey=): void $func
*/
private function eachInternal(callable $func): void
{
if ($this->empty()) {
return;
}

$func = self::wrapInternalCallable($func);

if (is_array($this->pipeline)) {
// 5% faster
array_walk($this->pipeline, $func);
return;
}

foreach ($this->pipeline as $key => $value) {
try {
$func($value, $key);
} catch (ArgumentCountError) {
// Optimization to reduce the number of argument count errors when calling internal callables.
// This error is thrown when too many arguments are passed to a built-in function (that are sensitive
// to extra arguments), so we can wrap it to prevent the errors later. On the other hand, if there
// are too little arguments passed, it will blow up just a line later.
$func = self::wrapInternalCallable($func);
$func($value, $key);
}
$func($value, $key);
}
}

/**
* Wraps internal functions with strict arity with a callable to prevent ArgumentCountError.
*/
private static function wrapInternalCallable(callable $func): callable
{
return static fn($value) => $func($value);
$ref = new ReflectionFunction($func(...));

if ($ref->isUserDefined()) {
return $func;
}

if ($ref->isVariadic()) {
return $func;
}

if (1 !== $ref->getNumberOfParameters()) {
return $func;
}

// User-defined functions silently ignore extra args
return fn($a) => $func($a);
}
}
21 changes: 0 additions & 21 deletions tests/EachTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
use Pipeline\Standard;
use ArrayIterator;
use SplQueue;
use Tests\Pipeline\Fixtures\CallableThrower;

use function Pipeline\map;
use function Pipeline\take;
Expand Down Expand Up @@ -195,24 +194,4 @@ public function testArgumentCountError(): void
$this->expectExceptionMessage('Too few arguments');
$pipeline->each(static function ($a, $b, $c): void {});
}

/**
* Test that the reassignment of the callable inside the loop will affect all iterations.
*/
public function testCallableReassigned(): void
{
$callback = new CallableThrower();

$pipeline = fromArray(['1', '2', '3']);
$pipeline->each($callback);

$this->assertSame(4, $callback->callCount, 'Expected 1 initial call that throws + 3 successful calls after wrapping');

$this->assertSame([
['1', 0],
['1'],
['2'],
['3'],
], $callback->args);
}
}
38 changes: 0 additions & 38 deletions tests/Fixtures/CallableThrower.php

This file was deleted.