diff --git a/rector.php b/rector.php index b145f79..3231b06 100644 --- a/rector.php +++ b/rector.php @@ -3,7 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\PHPUnit\Set\PHPUnitSetList; +use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector; return RectorConfig::configure() ->withPhpSets(php81: true) @@ -15,8 +15,11 @@ privatization: true, typeDeclarations: true ) - ->withSets([ - PHPUnitSetList::PHPUNIT_100, + ->withComposerBased(phpunit: true) + ->withSkip([ + BoolReturnTypeFromBooleanStrictReturnsRector::class => [ + __DIR__ . '/tests/FilterTest.php', + ], ]) ->withParallel() ->withRootFiles() diff --git a/src/Assert/Filter.php b/src/Assert/Filter.php new file mode 100644 index 0000000..1a46930 --- /dev/null +++ b/src/Assert/Filter.php @@ -0,0 +1,45 @@ +getReturnType(); + + if (! $returnType instanceof ReflectionNamedType) { + throw new InvalidArgumentException('Expected a bool return type on callable filter, null given'); + } + + $returnTypeName = $returnType->getName(); + if ($returnTypeName !== 'bool') { + throw new InvalidArgumentException(sprintf( + 'Expected a bool return type on callable filter, %s given', + $returnTypeName + )); + } + } +} diff --git a/src/AtLeast.php b/src/AtLeast.php index dada2db..14c76de 100644 --- a/src/AtLeast.php +++ b/src/AtLeast.php @@ -4,6 +4,7 @@ namespace ArrayLookup; +use ArrayLookup\Assert\Filter; use Traversable; use Webmozart\Assert\Assert; @@ -47,14 +48,13 @@ private static function atLeastFoundTimes( ): bool { // usage must be higher than 0 Assert::greaterThan($maxCount, 0); + // filter must be a callable with bool return type + Filter::boolean($filter); $totalFound = 0; foreach ($data as $key => $datum) { $isFound = $filter($datum, $key); - // returns of callable must be bool - Assert::boolean($isFound); - if (! $isFound) { continue; } diff --git a/src/Collector.php b/src/Collector.php index 23eb475..6ad5ae9 100644 --- a/src/Collector.php +++ b/src/Collector.php @@ -4,6 +4,7 @@ namespace ArrayLookup; +use ArrayLookup\Assert\Filter; use Traversable; use Webmozart\Assert\Assert; @@ -67,20 +68,22 @@ public function getResults(): array // ensure transform property is set early ->withTransform() method Assert::isCallable($this->transform); - $count = 0; - $collectedData = []; - $isCallableWhen = is_callable($this->when); + $count = 0; + $collectedData = []; + + if (is_callable($this->when)) { + // filter must be a callable with bool return type + Filter::boolean($this->when); + } foreach ($this->data as $key => $datum) { - if ($isCallableWhen) { + if ($this->when !== null) { /** * @var callable(mixed $datum, int|string|null $key): bool $when */ $when = $this->when; $isFound = ($when)($datum, $key); - Assert::boolean($isFound); - if (! $isFound) { continue; } diff --git a/src/Finder.php b/src/Finder.php index c13c8a6..7fbeb37 100644 --- a/src/Finder.php +++ b/src/Finder.php @@ -5,6 +5,7 @@ namespace ArrayLookup; use ArrayIterator; +use ArrayLookup\Assert\Filter; use ArrayObject; use Traversable; use Webmozart\Assert\Assert; @@ -24,12 +25,12 @@ final class Finder */ public static function first(iterable $data, callable $filter, bool $returnKey = false): mixed { + // filter must be a callable with bool return type + Filter::boolean($filter); + foreach ($data as $key => $datum) { $isFound = $filter($datum, $key); - // returns of callable must be bool - Assert::boolean($isFound); - if (! $isFound) { continue; } @@ -71,6 +72,9 @@ public static function last( // ensure data is array for end(), key(), current(), prev() usage Assert::isArray($data); + // filter must be a callable with bool return type + Filter::boolean($filter); + // Use end(), key(), current(), prev() usage instead of array_reverse() // to avoid immediatelly got "Out of memory" on many data // see https://3v4l.org/IHo2H vs https://3v4l.org/Wqejc @@ -91,9 +95,6 @@ public static function last( $current = current($data); $isFound = $filter($current, $key); - // returns of callable must be bool - Assert::boolean($isFound); - if (! $isFound) { // go to previous row prev($data); @@ -133,12 +134,12 @@ public static function rows( $newKey = 0; $totalFound = 0; + // filter must be a callable with bool return type + Filter::boolean($filter); + foreach ($data as $key => $datum) { $isFound = $filter($datum, $key); - // returns of callable must be bool - Assert::boolean($isFound); - if (! $isFound) { continue; } diff --git a/src/Only.php b/src/Only.php index 83978fc..e296abd 100644 --- a/src/Only.php +++ b/src/Only.php @@ -4,6 +4,7 @@ namespace ArrayLookup; +use ArrayLookup\Assert\Filter; use Traversable; use Webmozart\Assert\Assert; @@ -47,14 +48,13 @@ private static function onlyFoundTimes( ): bool { // usage must be higher than 0 Assert::greaterThan($maxCount, 0); + // filter must be a callable with bool return type + Filter::boolean($filter); $totalFound = 0; foreach ($data as $key => $datum) { $isFound = $filter($datum, $key); - // returns of callable must be bool - Assert::boolean($isFound); - if (! $isFound) { continue; } diff --git a/tests/FilterTest.php b/tests/FilterTest.php new file mode 100644 index 0000000..ccebcd7 --- /dev/null +++ b/tests/FilterTest.php @@ -0,0 +1,71 @@ +assertTrue(AtLeast::once($data, $filter)); + } + + public function testOnceWithStringFilter(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected Closure or invokable object, string given'); + + $data = [1, 'f']; + $filter = 'is_string'; + + AtLeast::once($data, $filter); + } + + public function testWithoutReturnTypeCallable(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected a bool return type on callable filter, null given'); + + $data = [1, 2, 3]; + + // phpcs:disable + $filter = new class { + public function __invoke(int $datum) + { + return $datum === 1; + } + }; + // phpcs:enable + + AtLeast::once($data, $filter); + } + + public function testWithNonBoolReturnTypeCallable(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected a bool return type on callable filter, string given'); + + $data = [1, 2, 3]; + $filter = new class { + public function __invoke(int $datum): string + { + return 'test'; + } + }; + + AtLeast::once($data, $filter); + } +}