Skip to content

Commit fb4880c

Browse files
committed
feat: introduce EnumerableBehavior trait and enhance Sequence functionality with new traversal methods
**Detailed Description:** 1. **New `EnumerableBehavior` Trait:** - Added `EnumerableBehavior`, encapsulating reusable iteration logic for data structures. - Introduced `tap()` for executing side-effect callbacks on each element. - Introduced `each()` for controlled traversal with behavior signals (`Signal::BREAK` and `Signal::CONTINUE`). - Includes validations to ensure proper return types, throwing `WrongReturnTypeException` for invalid signals. 2. **Enhanced `Sequence` Class:** - Integrated the `EnumerableBehavior` trait to streamline traversal operations. - Updated `getIterator()` to utilize `entries()` from the storage, enabling seamless iteration. 3. **Core Interface Updates:** - Enhanced the `Enumerable` contract to include `tap()` and `each()` methods. - Simplifies compliance for all enumerable structures, fostering consistency across implementations. 4. **Refactored Testing Suite:** - Added dedicated tests in `SequenceTest` for `tap()` and `each()`: - Validating traversal behavior, including `Signal` handling and early termination. - Ensured exceptions are thrown for improper callback return types. - Validated storage functionality by testing `entries()` in `ArrStorageTest` and `GeneratorStorageTest`. 5. **Autoload and Classmap Enhancements:** - Registered `EnumerableBehavior` in both classmap and compiled classmap for efficient autoloading. **Key Benefits:** - Reusable iteration logic empowers extensibility, reducing code duplication across data structures. - New traversal methods (`tap()`, `each()`) enable more expressive and streamlined operations. - Comprehensive tests ensure robust and predictable behavior across enumerable implementations.
1 parent 94c3ff7 commit fb4880c

12 files changed

Lines changed: 286 additions & 7 deletions

File tree

dist/core.min.phar

1.24 KB
Binary file not shown.

dist/core.phar

5.56 KB
Binary file not shown.

src/support/autoload/classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
\FireHub\Core\Support\DataStructure\Storage::class => __DIR__.'/../../support/datastructure/firehub.Storage.php',
9292
\FireHub\Core\Support\DataStructure\Storage\ArrStorage::class => __DIR__.'/../../support/datastructure/storage/firehub.ArrStorage.php',
9393
\FireHub\Core\Support\DataStructure\Storage\GeneratorStorage::class => __DIR__.'/../../support/datastructure/storage/firehub.GeneratorStorage.php',
94+
\FireHub\Core\Support\DataStructure\Trait\EnumerableBehavior::class => __DIR__.'/../../support/datastructure/trait/firehub.EnumerableBehavior.php',
9495
\FireHub\Core\Support\DataStructure\Type\Linear::class => __DIR__.'/../../support/datastructure/type/firehub.Linear.php',
9596
\FireHub\Core\Support\LowLevel::class => __DIR__.'/../../support/firehub.LowLevel.php',
9697
\FireHub\Core\Support\LowLevel\Arr::class => __DIR__.'/../../support/lowlevel/firehub.Arr.php',

src/support/autoload/loader/firehub.CompiledClassmap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ public function __invoke (string $class):void {
296296
require __DIR__.'/../../../support/datastructure/storage/firehub.GeneratorStorage.php';
297297
return;
298298

299+
case \FireHub\Core\Support\DataStructure\Trait\EnumerableBehavior::class:
300+
require __DIR__.'/../../../support/datastructure/trait/firehub.EnumerableBehavior.php';
301+
return;
302+
299303
case \FireHub\Core\Support\DataStructure\Type\Linear::class:
300304
require __DIR__.'/../../../support/datastructure/type/firehub.Linear.php';
301305
return;

src/support/datastructure/abstraction/firehub.Collection.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,4 @@
2727
*
2828
* @extends \FireHub\Core\Support\DataStructure\Abstraction\Enumerable<TKey, TValue>
2929
*/
30-
interface Collection extends Enumerable {
31-
32-
}
30+
interface Collection extends Enumerable {}

src/support/datastructure/abstraction/firehub.Enumerable.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,33 @@
3131
*/
3232
interface Enumerable extends DataStructure, IteratorAggregate {
3333

34+
/**
35+
* ### Tap into the enumerable for side effects
36+
* @since 1.0.0
37+
*
38+
* @param callable(TValue, TKey):void $callback <p>
39+
* Function to call on each item in a data structure.
40+
* </p>
41+
*
42+
* @return $this
43+
*/
44+
public function tap (callable $callback):static;
45+
46+
/**
47+
* ### Call a user-generated function on each item in the data structure
48+
* @since 1.0.0
49+
*
50+
* @uses \FireHub\Core\Shared\Enums\ControlFlow\Signal::BREAK As signal.
51+
* @uses \FireHub\Core\Shared\Enums\ControlFlow\Signal::CONTINUE As signal.
52+
*
53+
* @param callable(TValue, TKey):(\FireHub\Core\Shared\Enums\ControlFlow\Signal::BREAK|\FireHub\Core\Shared\Enums\ControlFlow\Signal::CONTINUE) $callback <p>
54+
* Function to call on each item in a data structure.<br>
55+
* Return `Signal::BREAK` to stop iteration early.<br>
56+
* Return `Signal::CONTINUE` to continue iteration.
57+
* </p>
58+
*
59+
* @return void
60+
*/
61+
public function each (callable $callback):void;
62+
3463
}

src/support/datastructure/abstraction/firehub.Stream.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,4 @@
2727
*
2828
* @extends \FireHub\Core\Support\DataStructure\Abstraction\Enumerable<TKey, TValue>
2929
*/
30-
interface Stream extends Enumerable {
31-
32-
}
30+
interface Stream extends Enumerable {}

src/support/datastructure/collection/linear/firehub.Sequence.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Access\SequentialAccess, Behavior\Countable
2020
};
2121
use FireHub\Core\Support\DataStructure\Storage;
22+
use FireHub\Core\Support\DataStructure\Trait\EnumerableBehavior;
2223
use Traversable;
2324

2425
/**
@@ -36,6 +37,14 @@
3637
*/
3738
class Sequence implements Collection, Linear, Countable {
3839

40+
/**
41+
* ### Enumerable Behavior Trait
42+
* @since 1.0.0
43+
*
44+
* @use \FireHub\Core\Support\DataStructure\Trait\EnumerableBehavior<int, TValue>
45+
*/
46+
use EnumerableBehavior;
47+
3948
/**
4049
* ### Constructor
4150
* @since 1.0.0
@@ -79,10 +88,12 @@ public function count ():int {
7988
* @inheritDoc
8089
*
8190
* @since 1.0.0
91+
*
92+
* @uses \FireHub\Core\Support\DataStructure\Storage::entries() To get the entries of the storage for iteration.
8293
*/
8394
public function getIterator ():Traversable {
8495

85-
yield from [];
96+
yield from $this->storage->entries();
8697

8798
}
8899

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php declare(strict_types = 1);
2+
3+
/**
4+
* This file is part of the FireHub Project ecosystem
5+
*
6+
* @author Danijel Galić <danijel.galic@outlook.com>
7+
* @copyright 2026 The FireHub Project - All rights reserved
8+
* @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0
9+
*
10+
* @php-version 7.0
11+
* @package Core\Support
12+
*/
13+
14+
namespace FireHub\Core\Support\DataStructure\Trait;
15+
16+
use FireHub\Core\Shared\Enums\ControlFlow\Signal;
17+
use FireHub\Core\Throwable\Exception\DataStructure\WrongReturnTypeException;
18+
19+
/**
20+
* ### Enumerable Behavior Trait
21+
*
22+
* Reusable iteration behavior for data structures, providing common functional operations over any IteratorAggregate
23+
* implementation with a consistent traversal model.
24+
* @since 1.0.0
25+
*
26+
* @template TKey
27+
* @template TValue
28+
*/
29+
trait EnumerableBehavior {
30+
31+
/**
32+
* {@inheritDoc}
33+
*
34+
* <code>
35+
* use FireHub\Core\Support\DataStructure\DS;
36+
*
37+
* $ds = DS::sequence()->fromArray(['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']);
38+
*
39+
* $called = [];
40+
* $ds->each(
41+
* function ($value, $key) use (&$called):void {
42+
* $called[] = $value;
43+
* });
44+
*
45+
* print $called;
46+
*
47+
* // ['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']
48+
* </code>
49+
*
50+
* @since 1.0.0
51+
*/
52+
public function tap (callable $callback):static {
53+
54+
foreach ($this as $key => $value)
55+
$callback($value, $key);
56+
57+
return $this;
58+
59+
}
60+
61+
/**
62+
* {@inheritDoc}
63+
*
64+
* <code>
65+
* use FireHub\Core\Support\DataStructure\DS;
66+
* use FireHub\Core\Shared\Enums\ControlFlow\Signal;
67+
*
68+
* $ds = DS::sequence()->fromArray(['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']);
69+
*
70+
* $called = [];
71+
* $ds->each(
72+
* function ($value, $key) use (&$called):Signal {
73+
* $called[] = $value;
74+
* return Signal::CONTINUE;
75+
* });
76+
*
77+
* print $called;
78+
*
79+
* // ['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']
80+
* </code>
81+
*
82+
* You can also stop at any time with returning Signal::BREAK:
83+
* <code>
84+
* use FireHub\Core\Support\DataStructure\DS;
85+
* use FireHub\Core\Shared\Enums\ControlFlow\Signal;
86+
*
87+
* $ds = DS::sequence()->fromArray(['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']);
88+
*
89+
* $called = [];
90+
* $ds->each(
91+
* function ($value, $key) use (&$called):Signal {
92+
* if ($value === 'Richard') return Signal::BREAK;
93+
* echo $value.',';
94+
* return Signal::CONTINUE;
95+
* });
96+
*
97+
* print $called;
98+
*
99+
* // ['John', 'Jane', 'Jane', 'Jane']
100+
* </code>
101+
*
102+
* @since 1.0.0
103+
*
104+
* @throws \FireHub\Core\Shared\Contracts\Throwable
105+
* @throws \FireHub\Core\Throwable\Exception\DataStructure\WrongReturnTypeException If the callback returns an
106+
* invalid signal.
107+
*/
108+
public function each (callable $callback):void {
109+
110+
foreach ($this as $key => $value) {
111+
112+
$signal = $callback($value, $key);
113+
114+
if ($signal === Signal::BREAK) break;
115+
if ($signal === Signal::CONTINUE) continue;
116+
throw WrongReturnTypeException::builder()
117+
->withContext([
118+
'type' => $signal
119+
])
120+
->build();
121+
122+
}
123+
124+
}
125+
126+
}

tests/unit/support/datastructure/collection/linear/SequenceTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use FireHub\Core\Support\DataStructure\DS;
1717
use FireHub\Core\Support\DataStructure\Builder\SequenceBuilder;
1818
use FireHub\Core\Support\DataStructure\Collection\Linear\Sequence;
19+
use FireHub\Core\Shared\Enums\ControlFlow\Signal;
20+
use FireHub\Core\Throwable\Exception\DataStructure\WrongReturnTypeException;
1921
use PHPUnit\Framework\Attributes\ {
2022
CoversClass, Group, Small, TestWith
2123
};
@@ -46,4 +48,82 @@ public function testCount (int $expected, array $array):void {
4648

4749
}
4850

51+
/**
52+
* @since 1.0.0
53+
*
54+
* @param array $array
55+
*
56+
* @return void
57+
*/
58+
#[TestWith([['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']])]
59+
public function testTap (array $array):void {
60+
61+
$ds = DS::sequence()->fromArray($array);
62+
63+
$called = [];
64+
$ds->tap(function ($value, $key) use (&$called):void {
65+
$called[] = $value;
66+
});
67+
68+
self::assertSame(['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard'], $called);
69+
70+
}
71+
72+
/**
73+
* @since 1.0.0
74+
*
75+
* @param array $array
76+
*
77+
* @throws \FireHub\Core\Shared\Contracts\Throwable
78+
* @throws \FireHub\Core\Throwable\Exception\DataStructure\WrongReturnTypeException
79+
*
80+
* @return void
81+
*/
82+
#[TestWith([['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']])]
83+
public function testEach (array $array):void {
84+
85+
$ds = DS::sequence()->fromArray($array);
86+
87+
$called = [];
88+
$ds->each(function ($value, $key) use (&$called):Signal {
89+
$called[] = $value;
90+
return Signal::CONTINUE;
91+
});
92+
93+
self::assertSame(['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard'], $called);
94+
95+
$called = [];
96+
$ds->each(function ($value, $key) use (&$called):Signal {
97+
if ($value === 'Richard') return Signal::BREAK;
98+
$called[] = $value;
99+
return Signal::CONTINUE;
100+
});
101+
102+
self::assertSame(['John', 'Jane', 'Jane', 'Jane'], $called);
103+
104+
}
105+
106+
/**
107+
* @since 1.0.0
108+
*
109+
* @param array $array
110+
*
111+
* @throws \FireHub\Core\Shared\Contracts\Throwable
112+
*
113+
* @return void
114+
*/
115+
#[TestWith([['John', 'Jane', 'Jane', 'Jane', 'Richard', 'Richard']])]
116+
public function testEachWrongReturnSignal (array $array):void {
117+
118+
$this->expectException(WrongReturnTypeException::class);
119+
120+
$called = [];
121+
DS::sequence()->fromArray($array)->each(function ($value, $key) use (&$called) {
122+
if ($value === 'Richard') return false;
123+
$called[] = $value;
124+
return Signal::CONTINUE;
125+
});
126+
127+
}
128+
49129
}

0 commit comments

Comments
 (0)