Skip to content

Commit 42fc535

Browse files
committed
refactor: move filter / map logic into IterableObject
1 parent 4c46851 commit 42fc535

File tree

7 files changed

+189
-240
lines changed

7 files changed

+189
-240
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"php": "^7.3 || ^8.0"
2222
},
2323
"require-dev": {
24+
"bentools/cartesian-product": "^1.3",
2425
"doctrine/coding-standard": "^8.2",
2526
"pestphp/pest": "^1.0",
2627
"phpstan/extension-installer": "^1.1",

src/IterableObject.php

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44

55
namespace BenTools\IterableFunctions;
66

7-
use EmptyIterator;
7+
use CallbackFilterIterator;
88
use IteratorAggregate;
9+
use IteratorIterator;
910
use Traversable;
1011

12+
use function array_filter;
13+
use function array_map;
14+
use function iterator_to_array;
15+
1116
/**
1217
* @internal
1318
*
@@ -18,73 +23,51 @@ final class IterableObject implements IteratorAggregate
1823
/** @var iterable<mixed> */
1924
private $iterable;
2025

21-
/** @var callable|null */
22-
private $filterFn;
23-
24-
/** @var callable|null */
25-
private $mapFn;
26-
27-
/**
28-
* @param iterable<mixed>|null $iterable
29-
*/
30-
private function __construct(?iterable $iterable = null, ?callable $filter = null, ?callable $map = null)
31-
{
32-
$this->iterable = $iterable ?? new EmptyIterator();
33-
$this->filterFn = $filter;
34-
$this->mapFn = $map;
35-
}
36-
3726
/**
38-
* @param iterable<mixed>|null $iterable
27+
* @param iterable<mixed> $iterable
3928
*/
40-
public static function new(?iterable $iterable = null): self
29+
public function __construct(iterable $iterable)
4130
{
42-
return new self($iterable);
31+
$this->iterable = $iterable;
4332
}
4433

4534
public function filter(?callable $filter = null): self
4635
{
47-
$filter = $filter ?? function ($value): bool {
48-
return (bool) $value;
49-
};
36+
if ($this->iterable instanceof Traversable) {
37+
$filter = $filter ??
38+
/** @param mixed $value */
39+
static function ($value): bool {
40+
return (bool) $value;
41+
};
42+
43+
return new self(new CallbackFilterIterator(new IteratorIterator($this->iterable), $filter));
44+
}
45+
46+
$filtered = $filter === null ? array_filter($this->iterable) : array_filter($this->iterable, $filter);
5047

51-
return new self($this->iterable, $filter, $this->mapFn);
48+
return new self($filtered);
5249
}
5350

54-
public function map(callable $map): self
51+
public function map(callable $mapper): self
5552
{
56-
return new self($this->iterable, $this->filterFn, $map);
53+
if ($this->iterable instanceof Traversable) {
54+
return new self(new MappedTraversable($this->iterable, $mapper));
55+
}
56+
57+
return new self(array_map($mapper, $this->iterable));
5758
}
5859

5960
/**
6061
* @return Traversable<mixed>
6162
*/
6263
public function getIterator(): Traversable
6364
{
64-
$iterable = $this->iterable;
65-
if ($this->filterFn !== null) {
66-
$iterable = iterable_filter($iterable, $this->filterFn);
67-
}
68-
69-
if ($this->mapFn !== null) {
70-
$iterable = iterable_map($iterable, $this->mapFn);
71-
}
72-
73-
return iterable_to_traversable($iterable);
65+
yield from $this->iterable;
7466
}
7567

7668
/** @return array<mixed> */
7769
public function asArray(): array
7870
{
79-
$iterable = $this->iterable;
80-
if ($this->filterFn !== null) {
81-
$iterable = iterable_filter($iterable, $this->filterFn);
82-
}
83-
84-
if ($this->mapFn !== null) {
85-
$iterable = iterable_map($iterable, $this->mapFn);
86-
}
87-
88-
return iterable_to_array($iterable);
71+
return $this->iterable instanceof Traversable ? iterator_to_array($this->iterable) : $this->iterable;
8972
}
9073
}

src/MappedTraversable.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BenTools\IterableFunctions;
6+
7+
use IteratorAggregate;
8+
use Traversable;
9+
10+
/**
11+
* @internal
12+
*
13+
* @implements IteratorAggregate<mixed>
14+
*/
15+
final class MappedTraversable implements IteratorAggregate
16+
{
17+
/** @var Traversable<mixed> */
18+
private $traversable;
19+
20+
/** @var callable */
21+
private $mapper;
22+
23+
/**
24+
* @param Traversable<mixed> $traversable
25+
*/
26+
public function __construct(Traversable $traversable, callable $mapper)
27+
{
28+
$this->traversable = $traversable;
29+
$this->mapper = $mapper;
30+
}
31+
32+
/**
33+
* @return Traversable<mixed>
34+
*/
35+
public function getIterator(): Traversable
36+
{
37+
foreach ($this->traversable as $key => $value) {
38+
yield $key => ($this->mapper)($value);
39+
}
40+
}
41+
}

src/iterable-functions.php

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
namespace BenTools\IterableFunctions;
66

77
use ArrayIterator;
8-
use CallbackFilterIterator;
9-
use IteratorIterator;
8+
use EmptyIterator;
109
use Traversable;
1110

12-
use function array_filter;
1311
use function array_values;
12+
use function is_array;
1413
use function iterator_to_array;
1514

1615
/**
@@ -22,9 +21,9 @@
2221
*/
2322
function iterable_map(iterable $iterable, callable $map): iterable
2423
{
25-
foreach ($iterable as $key => $item) {
26-
yield $key => $map($item);
27-
}
24+
$mapped = iterable($iterable)->map($map);
25+
26+
return is_array($iterable) ? $mapped->asArray() : $mapped;
2827
}
2928

3029
/**
@@ -65,23 +64,13 @@ function iterable_to_traversable(iterable $iterable): Traversable
6564
*
6665
* @param iterable<mixed> $iterable
6766
*
68-
* @return array<mixed>|CallbackFilterIterator
67+
* @return iterable<mixed>
6968
*/
70-
function iterable_filter(iterable $iterable, ?callable $filter = null)
69+
function iterable_filter(iterable $iterable, ?callable $filter = null): iterable
7170
{
72-
if ($filter === null) {
73-
$filter =
74-
/** @param mixed $value */
75-
static function ($value): bool {
76-
return (bool) $value;
77-
};
78-
}
79-
80-
if ($iterable instanceof Traversable) {
81-
return new CallbackFilterIterator(new IteratorIterator($iterable), $filter);
82-
}
71+
$filtered = iterable($iterable)->filter($filter);
8372

84-
return array_filter($iterable, $filter);
73+
return is_array($iterable) ? $filtered->asArray() : $filtered;
8574
}
8675

8776
/**
@@ -111,7 +100,7 @@ function iterable_reduce(iterable $iterable, callable $reduce, $initial = null)
111100
/**
112101
* @param iterable<mixed> $iterable
113102
*/
114-
function iterable(iterable $iterable): IterableObject
103+
function iterable(?iterable $iterable): IterableObject
115104
{
116-
return IterableObject::new($iterable);
105+
return new IterableObject($iterable ?? new EmptyIterator());
117106
}

tests/IterableFilterTest.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@
55
namespace BenTools\IterableFunctions\Tests;
66

77
use SplFixedArray;
8+
use Traversable;
89

10+
use function assert;
911
use function BenTools\IterableFunctions\iterable_filter;
10-
use function BenTools\IterableFunctions\iterable_to_array;
1112
use function it;
12-
use function PHPUnit\Framework\assertEquals;
13+
use function iterator_to_array;
14+
use function PHPUnit\Framework\assertSame;
1315

1416
it('filters an array', function (): void {
1517
$iterable = [false, true];
16-
assertEquals([1 => true], iterable_to_array(iterable_filter($iterable)));
18+
assertSame([1 => true], iterable_filter($iterable));
1719
});
1820

1921
it('filters a Traversable object', function (): void {
2022
$iterable = SplFixedArray::fromArray([false, true]);
21-
assertEquals([1 => true], iterable_to_array(iterable_filter($iterable)));
23+
$filtered = iterable_filter($iterable);
24+
assert($filtered instanceof Traversable);
25+
assertSame([1 => true], iterator_to_array($filtered));
2226
});
2327

2428
it('filters an array with a callback', function (): void {
@@ -28,7 +32,7 @@
2832
static function ($input): bool {
2933
return $input === 'bar';
3034
};
31-
assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter)));
35+
assertSame([1 => 'bar'], iterable_filter($iterable, $filter));
3236
});
3337

3438
it('filters a Travsersable object with a callback', function (): void {
@@ -38,5 +42,7 @@ static function ($input): bool {
3842
static function ($input): bool {
3943
return $input === 'bar';
4044
};
41-
assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter)));
45+
$filtered = iterable_filter($iterable, $filter);
46+
assert($filtered instanceof Traversable);
47+
assertSame([1 => 'bar'], iterator_to_array($filtered));
4248
});

tests/IterableMapTest.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,27 @@
88
use PHPUnit\Framework\Assert;
99
use SplFixedArray;
1010
use stdClass;
11+
use Traversable;
1112

13+
use function assert;
1214
use function BenTools\IterableFunctions\iterable_map;
13-
use function BenTools\IterableFunctions\iterable_to_array;
1415
use function it;
15-
use function PHPUnit\Framework\assertEquals;
16+
use function iterator_to_array;
1617
use function PHPUnit\Framework\assertInstanceOf;
1718
use function PHPUnit\Framework\assertSame;
1819

1920
it('maps an array', function (): void {
2021
$iterable = ['foo', 'bar'];
2122
$map = 'strtoupper';
22-
assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map)));
23+
assertSame(['FOO', 'BAR'], iterable_map($iterable, $map));
2324
});
2425

2526
it('maps a Traversable object', function (): void {
2627
$iterable = SplFixedArray::fromArray(['foo', 'bar']);
2728
$map = 'strtoupper';
28-
assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map)));
29+
$mapped = iterable_map($iterable, $map);
30+
assert($mapped instanceof Traversable);
31+
assertSame(['FOO', 'BAR'], iterator_to_array($mapped));
2932
});
3033

3134
it('maps iterable with object keys', function (): void {

0 commit comments

Comments
 (0)