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

Version 2: 🚀 Faster process with early validate filter before loop #29

Merged
merged 9 commits into from
Dec 28, 2024
Merged
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
9 changes: 6 additions & 3 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -15,8 +15,11 @@
privatization: true,
typeDeclarations: true
)
->withSets([
PHPUnitSetList::PHPUNIT_100,
->withComposerBased(phpunit: true)
->withSkip([
BoolReturnTypeFromBooleanStrictReturnsRector::class => [
__DIR__ . '/tests/FilterTest.php',
],
])
->withParallel()
->withRootFiles()
Expand Down
45 changes: 45 additions & 0 deletions src/Assert/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace ArrayLookup\Assert;

use Closure;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionNamedType;

use function gettype;
use function is_object;
use function sprintf;

final class Filter
{
public static function boolean(callable $filter): void
{
if ($filter instanceof Closure) {
$reflection = new ReflectionFunction($filter);
} elseif (is_object($filter)) {
$reflection = new ReflectionMethod($filter, '__invoke');
} else {
throw new InvalidArgumentException(
sprintf('Expected Closure or invokable object, %s given', gettype($filter))
);
}

$returnType = $reflection->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
));
}
}
}
6 changes: 3 additions & 3 deletions src/AtLeast.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace ArrayLookup;

use ArrayLookup\Assert\Filter;
use Traversable;
use Webmozart\Assert\Assert;

Expand Down Expand Up @@ -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;
}
Expand Down
15 changes: 9 additions & 6 deletions src/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace ArrayLookup;

use ArrayLookup\Assert\Filter;
use Traversable;
use Webmozart\Assert\Assert;

Expand Down Expand Up @@ -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;
}
Expand Down
19 changes: 10 additions & 9 deletions src/Finder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace ArrayLookup;

use ArrayIterator;
use ArrayLookup\Assert\Filter;
use ArrayObject;
use Traversable;
use Webmozart\Assert\Assert;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Only.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace ArrayLookup;

use ArrayLookup\Assert\Filter;
use Traversable;
use Webmozart\Assert\Assert;

Expand Down Expand Up @@ -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;
}
Expand Down
71 changes: 71 additions & 0 deletions tests/FilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace ArrayLookup\Tests;

use ArrayLookup\AtLeast;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;

final class FilterTest extends TestCase
{
public function testOnceWithFilterInvokableClass(): void
{
$data = [1, 2, 3];
$filter = new class {
public function __invoke(int $datum): bool
{
return $datum === 1;
}
};

$this->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);
}
}
Loading