44
55namespace GeekCell \Ddd \Domain ;
66
7+ use ArrayAccess ;
8+ use ArrayIterator ;
79use Assert ;
10+ use Countable ;
11+ use InvalidArgumentException ;
12+ use IteratorAggregate ;
13+ use Traversable ;
14+ use function array_filter ;
15+ use function array_map ;
16+ use function array_reduce ;
17+ use function array_values ;
18+ use function count ;
19+ use function get_class ;
20+ use function is_int ;
21+ use function reset ;
822
923/**
1024 * @template T of object
11- * @implements \ IteratorAggregate<T>
12- * @implements \ ArrayAccess<mixed, T>
25+ * @implements IteratorAggregate<T>
26+ * @implements ArrayAccess<mixed, T>
1327 */
14- class Collection implements \ ArrayAccess, \ Countable, \ IteratorAggregate
28+ class Collection implements ArrayAccess, Countable, IteratorAggregate
1529{
1630 /**
1731 * @param T[] $items
@@ -26,11 +40,218 @@ final public function __construct(
2640 }
2741 }
2842
43+ /**
44+ * Creates a collection from a given iterable of items.
45+ * This function is useful when trying to create a collection from a generator or an iterator.
46+ *
47+ * @param iterable<T> $items
48+ * @param class-string<T>|null $itemType
49+ * @return self<T>
50+ * @throws Assert\AssertionFailedException
51+ */
52+ public static function fromIterable (iterable $ items , ?string $ itemType = null ): static
53+ {
54+ if (is_array ($ items )) {
55+ return new static ($ items , $ itemType );
56+ }
57+
58+ if (!$ items instanceof Traversable) {
59+ $ items = [...$ items ];
60+ }
61+
62+ return new static (iterator_to_array ($ items ), $ itemType );
63+ }
64+
65+ /**
66+ * Returns true if every value in the collection passes the callback truthy test. Opposite of self::none().
67+ * Callback arguments will be element, index, collection.
68+ * Function short-circuits on first falsy return value.
69+ *
70+ * @param ?callable(T, int, static): bool $callback
71+ * @return bool
72+ */
73+ public function every (callable $ callback = null ): bool
74+ {
75+ if ($ callback === null ) {
76+ $ callback = static fn ($ item , $ index , $ self ) => $ item ;
77+ }
78+
79+ foreach ($ this ->items as $ index => $ item ) {
80+ if (!$ callback ($ item , $ index , $ this )) {
81+ return false ;
82+ }
83+ }
84+
85+ return true ;
86+ }
87+
88+ /**
89+ * Returns true if every value in the collection passes the callback falsy test. Opposite of self::every().
90+ * Callback arguments will be element, index, collection.
91+ * Function short-circuits on first truthy return value.
92+ *
93+ * @param ?callable(T, int, static): bool $callback
94+ * @return bool
95+ */
96+ public function none (callable $ callback = null ): bool
97+ {
98+ if ($ callback === null ) {
99+ $ callback = static fn ($ item , $ index , $ self ) => $ item ;
100+ }
101+
102+ foreach ($ this ->items as $ index => $ item ) {
103+ if ($ callback ($ item , $ index , $ this )) {
104+ return false ;
105+ }
106+ }
107+
108+ return true ;
109+ }
110+
111+ /**
112+ * Returns true if at least one value in the collection passes the callback truthy test.
113+ * Callback arguments will be element, index, collection.
114+ * Function short-circuits on first truthy return value.
115+ *
116+ * @param ?callable(T, int, static): bool $callback
117+ * @return bool
118+ */
119+ public function some (callable $ callback = null ): bool
120+ {
121+ if ($ callback === null ) {
122+ $ callback = static fn ($ item , $ index , $ self ) => $ item ;
123+ }
124+
125+ foreach ($ this ->items as $ index => $ item ) {
126+ if ($ callback ($ item , $ index , $ this )) {
127+ return true ;
128+ }
129+ }
130+
131+ return false ;
132+ }
133+
134+ /**
135+ * Returns the first element of the collection that matches the given callback.
136+ * If no callback is given the first element in the collection is returned.
137+ * Throws exception if collection is empty or the given callback was never satisfied.
138+ *
139+ * @param ?callable(T, int, static): bool $callback
140+ * @return T
141+ * @throws InvalidArgumentException
142+ */
143+ public function first (callable $ callback = null )
144+ {
145+ if ($ this ->items === []) {
146+ throw new InvalidArgumentException ('No items in collection ' );
147+ }
148+
149+ foreach ($ this ->items as $ index => $ item ) {
150+ if ($ callback === null || $ callback ($ item , $ index , $ this )) {
151+ return $ item ;
152+ }
153+ }
154+
155+ throw new InvalidArgumentException ('No item found in collection that satisfies first callback ' );
156+ }
157+
158+ /**
159+ * Returns the first element of the collection that matches the given callback.
160+ * If no callback is given the first element in the collection is returned.
161+ * If the collection is empty the given fallback value is returned instead.
162+ *
163+ * @template U of T|mixed
164+ * @param ?callable(T, int, static): bool $callback
165+ * @param U $fallbackValue
166+ * @return U
167+ * @throws InvalidArgumentException
168+ */
169+ public function firstOr (callable $ callback = null , mixed $ fallbackValue = null )
170+ {
171+ if ($ this ->items === []) {
172+ return $ fallbackValue ;
173+ }
174+
175+ foreach ($ this ->items as $ index => $ item ) {
176+ if ($ callback === null || $ callback ($ item , $ index , $ this )) {
177+ return $ item ;
178+ }
179+ }
180+
181+ return $ fallbackValue ;
182+ }
183+
184+ /**
185+ * Returns the last element of the collection that matches the given callback.
186+ * If no callback is given the last element in the collection is returned.
187+ * Throws exception if collection is empty or the given callback was never satisfied.
188+ *
189+ * @param ?callable(T, int, static): bool $callback
190+ * @return T
191+ * @throws InvalidArgumentException
192+ */
193+ public function last (callable $ callback = null )
194+ {
195+ if ($ this ->items === []) {
196+ throw new InvalidArgumentException ('No items in collection ' );
197+ }
198+
199+ foreach (array_reverse ($ this ->items ) as $ index => $ item ) {
200+ if ($ callback === null || $ callback ($ item , $ index , $ this )) {
201+ return $ item ;
202+ }
203+ }
204+
205+ throw new InvalidArgumentException ('No item found in collection that satisfies last callback ' );
206+ }
207+
208+ /**
209+ * Returns the last element of the collection that matches the given callback.
210+ * If no callback is given the last element in the collection is returned.
211+ * If the collection is empty the given fallback value is returned instead.
212+ *
213+ * @template U of T|mixed
214+ * @param ?callable(T, int, static): bool $callback
215+ * @param U $fallbackValue
216+ * @return U
217+ * @throws InvalidArgumentException
218+ */
219+ public function lastOr (callable $ callback = null , mixed $ fallbackValue = null )
220+ {
221+ if ($ this ->items === []) {
222+ return $ fallbackValue ;
223+ }
224+
225+ foreach (array_reverse ($ this ->items ) as $ index => $ item ) {
226+ if ($ callback === null || $ callback ($ item , $ index , $ this )) {
227+ return $ item ;
228+ }
229+ }
230+
231+ return $ fallbackValue ;
232+ }
233+
234+ /**
235+ * Returns whether the collection is empty (has no items)
236+ */
237+ public function isEmpty (): bool
238+ {
239+ return $ this ->items === [];
240+ }
241+
242+ /**
243+ * Returns whether the collection has items
244+ */
245+ public function hasItems (): bool
246+ {
247+ return $ this ->items !== [];
248+ }
249+
29250 /**
30251 * Add one or more items to the collection. It **does not** modify the
31252 * current collection, but returns a new one.
32253 *
33- * @param mixed $item One or more items to add to the collection.
254+ * @param T|iterable<T> $item One or more items to add to the collection.
34255 * @return static
35256 */
36257 public function add (mixed $ item ): static
@@ -56,7 +277,7 @@ public function add(mixed $item): static
56277 public function filter (callable $ callback ): static
57278 {
58279 return new static (
59- \ array_values (\ array_filter ($ this ->items , $ callback )),
280+ array_values (array_filter ($ this ->items , $ callback )),
60281 $ this ->itemType ,
61282 );
62283 }
@@ -74,15 +295,15 @@ public function filter(callable $callback): static
74295 */
75296 public function map (callable $ callback , bool $ inferTypes = true ): static
76297 {
77- $ mapResult = \ array_map ($ callback , $ this ->items );
78- $ firstItem = \ reset ($ mapResult );
298+ $ mapResult = array_map ($ callback , $ this ->items );
299+ $ firstItem = reset ($ mapResult );
79300
80301 if ($ firstItem === false || !is_object ($ firstItem )) {
81302 return new static ($ mapResult );
82303 }
83304
84305 if ($ inferTypes && $ this ->itemType !== null ) {
85- return new static ($ mapResult , \ get_class ($ firstItem ));
306+ return new static ($ mapResult , get_class ($ firstItem ));
86307 }
87308
88309 return new static ($ mapResult );
@@ -98,15 +319,15 @@ public function map(callable $callback, bool $inferTypes = true): static
98319 */
99320 public function reduce (callable $ callback , mixed $ initial = null ): mixed
100321 {
101- return \ array_reduce ($ this ->items , $ callback , $ initial );
322+ return array_reduce ($ this ->items , $ callback , $ initial );
102323 }
103324
104325 /**
105326 * @inheritDoc
106327 */
107328 public function offsetExists (mixed $ offset ): bool
108329 {
109- if (!\ is_int ($ offset )) {
330+ if (!is_int ($ offset )) {
110331 return false ;
111332 }
112333
@@ -152,14 +373,14 @@ public function offsetUnset(mixed $offset): void
152373 */
153374 public function count (): int
154375 {
155- return \ count ($ this ->items );
376+ return count ($ this ->items );
156377 }
157378
158379 /**
159380 * @inheritDoc
160381 */
161- public function getIterator (): \ Traversable
382+ public function getIterator (): Traversable
162383 {
163- return new \ ArrayIterator ($ this ->items );
384+ return new ArrayIterator ($ this ->items );
164385 }
165386}
0 commit comments