Skip to content

Commit 7f4f836

Browse files
authored
Merge pull request #29 from samsonasik/v2-filter-early
Version 2: 🚀 Faster process with early validate filter before loop
2 parents 3029f92 + 29ea948 commit 7f4f836

File tree

7 files changed

+147
-24
lines changed

7 files changed

+147
-24
lines changed

rector.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6-
use Rector\PHPUnit\Set\PHPUnitSetList;
6+
use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector;
77

88
return RectorConfig::configure()
99
->withPhpSets(php81: true)
@@ -15,8 +15,11 @@
1515
privatization: true,
1616
typeDeclarations: true
1717
)
18-
->withSets([
19-
PHPUnitSetList::PHPUNIT_100,
18+
->withComposerBased(phpunit: true)
19+
->withSkip([
20+
BoolReturnTypeFromBooleanStrictReturnsRector::class => [
21+
__DIR__ . '/tests/FilterTest.php',
22+
],
2023
])
2124
->withParallel()
2225
->withRootFiles()

src/Assert/Filter.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ArrayLookup\Assert;
6+
7+
use Closure;
8+
use InvalidArgumentException;
9+
use ReflectionFunction;
10+
use ReflectionMethod;
11+
use ReflectionNamedType;
12+
13+
use function gettype;
14+
use function is_object;
15+
use function sprintf;
16+
17+
final class Filter
18+
{
19+
public static function boolean(callable $filter): void
20+
{
21+
if ($filter instanceof Closure) {
22+
$reflection = new ReflectionFunction($filter);
23+
} elseif (is_object($filter)) {
24+
$reflection = new ReflectionMethod($filter, '__invoke');
25+
} else {
26+
throw new InvalidArgumentException(
27+
sprintf('Expected Closure or invokable object, %s given', gettype($filter))
28+
);
29+
}
30+
31+
$returnType = $reflection->getReturnType();
32+
33+
if (! $returnType instanceof ReflectionNamedType) {
34+
throw new InvalidArgumentException('Expected a bool return type on callable filter, null given');
35+
}
36+
37+
$returnTypeName = $returnType->getName();
38+
if ($returnTypeName !== 'bool') {
39+
throw new InvalidArgumentException(sprintf(
40+
'Expected a bool return type on callable filter, %s given',
41+
$returnTypeName
42+
));
43+
}
44+
}
45+
}

src/AtLeast.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace ArrayLookup;
66

7+
use ArrayLookup\Assert\Filter;
78
use Traversable;
89
use Webmozart\Assert\Assert;
910

@@ -47,14 +48,13 @@ private static function atLeastFoundTimes(
4748
): bool {
4849
// usage must be higher than 0
4950
Assert::greaterThan($maxCount, 0);
51+
// filter must be a callable with bool return type
52+
Filter::boolean($filter);
5053

5154
$totalFound = 0;
5255
foreach ($data as $key => $datum) {
5356
$isFound = $filter($datum, $key);
5457

55-
// returns of callable must be bool
56-
Assert::boolean($isFound);
57-
5858
if (! $isFound) {
5959
continue;
6060
}

src/Collector.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace ArrayLookup;
66

7+
use ArrayLookup\Assert\Filter;
78
use Traversable;
89
use Webmozart\Assert\Assert;
910

@@ -67,20 +68,22 @@ public function getResults(): array
6768
// ensure transform property is set early ->withTransform() method
6869
Assert::isCallable($this->transform);
6970

70-
$count = 0;
71-
$collectedData = [];
72-
$isCallableWhen = is_callable($this->when);
71+
$count = 0;
72+
$collectedData = [];
73+
74+
if (is_callable($this->when)) {
75+
// filter must be a callable with bool return type
76+
Filter::boolean($this->when);
77+
}
7378

7479
foreach ($this->data as $key => $datum) {
75-
if ($isCallableWhen) {
80+
if ($this->when !== null) {
7681
/**
7782
* @var callable(mixed $datum, int|string|null $key): bool $when
7883
*/
7984
$when = $this->when;
8085
$isFound = ($when)($datum, $key);
8186

82-
Assert::boolean($isFound);
83-
8487
if (! $isFound) {
8588
continue;
8689
}

src/Finder.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ArrayLookup;
66

77
use ArrayIterator;
8+
use ArrayLookup\Assert\Filter;
89
use ArrayObject;
910
use Traversable;
1011
use Webmozart\Assert\Assert;
@@ -24,12 +25,12 @@ final class Finder
2425
*/
2526
public static function first(iterable $data, callable $filter, bool $returnKey = false): mixed
2627
{
28+
// filter must be a callable with bool return type
29+
Filter::boolean($filter);
30+
2731
foreach ($data as $key => $datum) {
2832
$isFound = $filter($datum, $key);
2933

30-
// returns of callable must be bool
31-
Assert::boolean($isFound);
32-
3334
if (! $isFound) {
3435
continue;
3536
}
@@ -71,6 +72,9 @@ public static function last(
7172
// ensure data is array for end(), key(), current(), prev() usage
7273
Assert::isArray($data);
7374

75+
// filter must be a callable with bool return type
76+
Filter::boolean($filter);
77+
7478
// Use end(), key(), current(), prev() usage instead of array_reverse()
7579
// to avoid immediatelly got "Out of memory" on many data
7680
// see https://3v4l.org/IHo2H vs https://3v4l.org/Wqejc
@@ -91,9 +95,6 @@ public static function last(
9195
$current = current($data);
9296
$isFound = $filter($current, $key);
9397

94-
// returns of callable must be bool
95-
Assert::boolean($isFound);
96-
9798
if (! $isFound) {
9899
// go to previous row
99100
prev($data);
@@ -133,12 +134,12 @@ public static function rows(
133134
$newKey = 0;
134135
$totalFound = 0;
135136

137+
// filter must be a callable with bool return type
138+
Filter::boolean($filter);
139+
136140
foreach ($data as $key => $datum) {
137141
$isFound = $filter($datum, $key);
138142

139-
// returns of callable must be bool
140-
Assert::boolean($isFound);
141-
142143
if (! $isFound) {
143144
continue;
144145
}

src/Only.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace ArrayLookup;
66

7+
use ArrayLookup\Assert\Filter;
78
use Traversable;
89
use Webmozart\Assert\Assert;
910

@@ -47,14 +48,13 @@ private static function onlyFoundTimes(
4748
): bool {
4849
// usage must be higher than 0
4950
Assert::greaterThan($maxCount, 0);
51+
// filter must be a callable with bool return type
52+
Filter::boolean($filter);
5053

5154
$totalFound = 0;
5255
foreach ($data as $key => $datum) {
5356
$isFound = $filter($datum, $key);
5457

55-
// returns of callable must be bool
56-
Assert::boolean($isFound);
57-
5858
if (! $isFound) {
5959
continue;
6060
}

tests/FilterTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ArrayLookup\Tests;
6+
7+
use ArrayLookup\AtLeast;
8+
use InvalidArgumentException;
9+
use PHPUnit\Framework\TestCase;
10+
11+
final class FilterTest extends TestCase
12+
{
13+
public function testOnceWithFilterInvokableClass(): void
14+
{
15+
$data = [1, 2, 3];
16+
$filter = new class {
17+
public function __invoke(int $datum): bool
18+
{
19+
return $datum === 1;
20+
}
21+
};
22+
23+
$this->assertTrue(AtLeast::once($data, $filter));
24+
}
25+
26+
public function testOnceWithStringFilter(): void
27+
{
28+
$this->expectException(InvalidArgumentException::class);
29+
$this->expectExceptionMessage('Expected Closure or invokable object, string given');
30+
31+
$data = [1, 'f'];
32+
$filter = 'is_string';
33+
34+
AtLeast::once($data, $filter);
35+
}
36+
37+
public function testWithoutReturnTypeCallable(): void
38+
{
39+
$this->expectException(InvalidArgumentException::class);
40+
$this->expectExceptionMessage('Expected a bool return type on callable filter, null given');
41+
42+
$data = [1, 2, 3];
43+
44+
// phpcs:disable
45+
$filter = new class {
46+
public function __invoke(int $datum)
47+
{
48+
return $datum === 1;
49+
}
50+
};
51+
// phpcs:enable
52+
53+
AtLeast::once($data, $filter);
54+
}
55+
56+
public function testWithNonBoolReturnTypeCallable(): void
57+
{
58+
$this->expectException(InvalidArgumentException::class);
59+
$this->expectExceptionMessage('Expected a bool return type on callable filter, string given');
60+
61+
$data = [1, 2, 3];
62+
$filter = new class {
63+
public function __invoke(int $datum): string
64+
{
65+
return 'test';
66+
}
67+
};
68+
69+
AtLeast::once($data, $filter);
70+
}
71+
}

0 commit comments

Comments
 (0)