Skip to content

Commit c8a31db

Browse files
gturpin-devshaffe-frinnocenzi
authored
feat(support): add methods reduce, chunk and findKey to ArrayHelper (#720)
Co-authored-by: Karel Faille <[email protected]> Co-authored-by: Enzo Innocenzi <[email protected]>
1 parent d599d50 commit c8a31db

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

src/Tempest/Support/src/ArrayHelper.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,75 @@ public function __construct(
4444
}
4545
}
4646

47+
/**
48+
* Finds a value in the array and return the corresponding key if successful.
49+
*
50+
* @param (Closure(TValue, TKey): bool)|mixed $value The value to search for, a Closure will find the first item that returns true.
51+
* @param bool $strict Whether to use strict comparison.
52+
*
53+
* @return array-key|null The key for `$value` if found, `null` otherwise.
54+
*/
55+
public function findKey(mixed $value, bool $strict = false): int|string|null
56+
{
57+
if (! $value instanceof Closure) {
58+
$search = array_search($value, $this->array, $strict);
59+
60+
return $search === false ? null : $search; // Keep empty values but convert false to null
61+
}
62+
63+
foreach ($this->array as $key => $item) {
64+
if ($value($item, $key) === true) {
65+
return $key;
66+
}
67+
}
68+
69+
return null;
70+
}
71+
72+
/**
73+
* Chunks the array into chunks of the given size.
74+
*
75+
* @param int $size The size of each chunk.
76+
* @param bool $preserveKeys Whether to preserve the keys of the original array.
77+
*
78+
* @return self<array-key, self>
79+
*/
80+
public function chunk(int $size, bool $preserveKeys = true): self
81+
{
82+
if ($size <= 0) {
83+
return new self();
84+
}
85+
86+
$chunks = [];
87+
foreach (array_chunk($this->array, $size, $preserveKeys) as $chunk) {
88+
$chunks[] = new self($chunk);
89+
}
90+
91+
return new self($chunks);
92+
}
93+
94+
/**
95+
* Reduces the array to a single value using a callback.
96+
*
97+
* @template TReduceInitial
98+
* @template TReduceReturnType
99+
*
100+
* @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
101+
* @param TReduceInitial $initial
102+
*
103+
* @return TReduceReturnType
104+
*/
105+
public function reduce(callable $callback, mixed $initial = null): mixed
106+
{
107+
$result = $initial;
108+
109+
foreach ($this->array as $key => $value) {
110+
$result = $callback($result, $value, $key);
111+
}
112+
113+
return $result;
114+
}
115+
47116
/**
48117
* Gets a value from the array and remove it.
49118
*

src/Tempest/Support/tests/ArrayHelperTest.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,4 +1381,185 @@ public function test_sort_keys_by_callback(): void
13811381
actual: $array->sortKeysByCallback(fn ($a, $b) => $a <=> $b)->toArray(),
13821382
);
13831383
}
1384+
1385+
public function test_basic_reduce(): void
1386+
{
1387+
$collection = arr([
1388+
'first_name' => 'John',
1389+
'last_name' => 'Doe',
1390+
'age' => 42,
1391+
]);
1392+
1393+
$this->assertSame(
1394+
actual: $collection->reduce(fn ($carry, $value) => $carry . ' ' . $value, 'Hello'),
1395+
expected: 'Hello John Doe 42',
1396+
);
1397+
}
1398+
1399+
public function test_reduce_with_existing_function(): void
1400+
{
1401+
$collection = arr([
1402+
[1, 2, 2, 3],
1403+
[2, 3, 3, 4],
1404+
[3, 1, 3, 1],
1405+
]);
1406+
1407+
$this->assertSame(
1408+
actual: $collection->reduce('max'),
1409+
expected: [3, 1, 3, 1],
1410+
);
1411+
}
1412+
1413+
public function test_empty_array_reduce(): void
1414+
{
1415+
$this->assertSame(
1416+
actual: arr()->reduce(fn ($carry, $value) => $carry . ' ' . $value, 'default'),
1417+
expected: 'default',
1418+
);
1419+
}
1420+
1421+
public function test_chunk(): void
1422+
{
1423+
$collection = arr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1424+
1425+
$this->assertSame(
1426+
actual: $collection
1427+
->chunk(2, preserveKeys: false)
1428+
->map(fn ($chunk) => $chunk->toArray())
1429+
->toArray(),
1430+
expected: [
1431+
[1, 2],
1432+
[3, 4],
1433+
[5, 6],
1434+
[7, 8],
1435+
[9, 10],
1436+
],
1437+
);
1438+
1439+
$this->assertSame(
1440+
actual: $collection
1441+
->chunk(3, preserveKeys: false)
1442+
->map(fn ($chunk) => $chunk->toArray())
1443+
->toArray(),
1444+
expected: [
1445+
[1, 2, 3],
1446+
[4, 5, 6],
1447+
[7, 8, 9],
1448+
[10],
1449+
],
1450+
);
1451+
}
1452+
1453+
public function test_chunk_preserve_keys(): void
1454+
{
1455+
$collection = arr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1456+
1457+
$this->assertSame(
1458+
actual: $collection
1459+
->chunk(2)
1460+
->map(fn ($chunk) => $chunk->toArray())
1461+
->toArray(),
1462+
expected: [
1463+
[0 => 1, 1 => 2],
1464+
[2 => 3, 3 => 4],
1465+
[4 => 5, 5 => 6],
1466+
[6 => 7, 7 => 8],
1467+
[8 => 9, 9 => 10],
1468+
],
1469+
);
1470+
1471+
$this->assertSame(
1472+
actual: $collection
1473+
->chunk(3)
1474+
->map(fn ($chunk) => $chunk->toArray())
1475+
->toArray(),
1476+
expected: [
1477+
[0 => 1, 1 => 2, 2 => 3],
1478+
[3 => 4, 4 => 5, 5 => 6],
1479+
[6 => 7, 7 => 8, 8 => 9],
1480+
[9 => 10],
1481+
],
1482+
);
1483+
}
1484+
1485+
public function test_find_key_with_simple_value(): void
1486+
{
1487+
$collection = arr(['apple', 'banana', 'orange']);
1488+
1489+
$this->assertSame(1, $collection->findKey('banana'));
1490+
$this->assertSame(0, $collection->findKey('apple'));
1491+
$this->assertNull($collection->findKey('grape'));
1492+
}
1493+
1494+
public function test_find_key_with_strict_comparison(): void
1495+
{
1496+
$collection = arr([1, '1', 2, '2']);
1497+
1498+
$this->assertSame(0, $collection->findKey(1, strict: false));
1499+
$this->assertSame(0, $collection->findKey('1', strict: false));
1500+
1501+
$this->assertSame(0, $collection->findKey(1, strict: true));
1502+
$this->assertSame(1, $collection->findKey('1', strict: true));
1503+
}
1504+
1505+
public function test_find_key_with_closure(): void
1506+
{
1507+
$collection = arr([
1508+
['id' => 1, 'name' => 'John'],
1509+
['id' => 2, 'name' => 'Jane'],
1510+
['id' => 3, 'name' => 'Bob'],
1511+
]);
1512+
1513+
$result = $collection->findKey(fn ($item) => $item['name'] === 'Jane');
1514+
$this->assertSame(1, $result);
1515+
1516+
$result = $collection->findKey(fn ($item, $key) => $key === 2);
1517+
$this->assertSame(2, $result);
1518+
1519+
$result = $collection->findKey(fn ($item) => $item['name'] === 'Alice');
1520+
$this->assertNull($result);
1521+
}
1522+
1523+
public function test_find_key_with_string_keys(): void
1524+
{
1525+
$collection = arr([
1526+
'first' => 'value1',
1527+
'second' => 'value2',
1528+
'third' => 'value3',
1529+
]);
1530+
1531+
$this->assertSame('second', $collection->findKey('value2'));
1532+
$this->assertNull($collection->findKey('value4'));
1533+
}
1534+
1535+
public function test_find_key_with_null_values(): void
1536+
{
1537+
$collection = arr(['a', null, 'b', '']);
1538+
1539+
$this->assertSame(1, $collection->findKey(null));
1540+
$this->assertSame(1, $collection->findKey(''));
1541+
}
1542+
1543+
public function test_find_key_with_complex_closure(): void
1544+
{
1545+
$collection = arr([
1546+
['age' => 25, 'active' => true],
1547+
['age' => 30, 'active' => false],
1548+
['age' => 35, 'active' => true],
1549+
]);
1550+
1551+
$result = $collection->findKey(function ($item) {
1552+
return $item['age'] > 28 && $item['active'] === true;
1553+
});
1554+
1555+
$this->assertSame(2, $result);
1556+
}
1557+
1558+
public function test_find_key_with_empty_array(): void
1559+
{
1560+
$collection = arr([]);
1561+
1562+
$this->assertNull($collection->findKey('anything'));
1563+
$this->assertNull($collection->findKey(fn () => true));
1564+
}
13841565
}

0 commit comments

Comments
 (0)