src/Collection.php source

1 <?php
2
3 namespace mini;
4
5 use ArrayIterator;
6 use Closure;
7 use mini\Contracts\CollectionInterface;
8 use Traversable;
9
10 /**
11 * Immutable collection with functional transformation methods
12 *
13 * All transformation methods return new Collection instances,
14 * leaving the original unchanged.
15 *
16 * @template T
17 * @implements CollectionInterface<T>
18 */
19 final class Collection implements CollectionInterface
20 {
21 /**
22 * @param array<int, T> $items
23 */
24 private function __construct(
25 private readonly array $items
26 ) {}
27
28 /**
29 * Create a collection from an iterable
30 *
31 * @template U
32 * @param iterable<U> $items
33 * @return self<U>
34 */
35 public static function from(iterable $items): self
36 {
37 if ($items instanceof self) {
38 return $items;
39 }
40
41 return new self(
42 $items instanceof Traversable
43 ? iterator_to_array($items, false)
44 : array_values($items)
45 );
46 }
47
48 /**
49 * Create an empty collection
50 *
51 * @template U
52 * @return self<U>
53 */
54 public static function empty(): self
55 {
56 return new self([]);
57 }
58
59 /**
60 * Create a collection from variadic arguments
61 *
62 * @template U
63 * @param U ...$items
64 * @return self<U>
65 */
66 public static function of(mixed ...$items): self
67 {
68 return new self($items);
69 }
70
71 /**
72 * @template U
73 * @param Closure(T): U $fn
74 * @return CollectionInterface<U>
75 */
76 public function map(Closure $fn): CollectionInterface
77 {
78 return new self(array_map($fn, $this->items));
79 }
80
81 /**
82 * @param Closure(T): bool $fn
83 * @return CollectionInterface<T>
84 */
85 public function filter(Closure $fn): CollectionInterface
86 {
87 return new self(array_values(array_filter($this->items, $fn)));
88 }
89
90 /**
91 * @return T|null
92 */
93 public function first(): mixed
94 {
95 return $this->items[0] ?? null;
96 }
97
98 /**
99 * @return T|null
100 */
101 public function last(): mixed
102 {
103 if (empty($this->items)) {
104 return null;
105 }
106 return $this->items[count($this->items) - 1];
107 }
108
109 public function isEmpty(): bool
110 {
111 return empty($this->items);
112 }
113
114 /**
115 * @template U
116 * @param Closure(U, T): U $fn
117 * @param U $initial
118 * @return U
119 */
120 public function reduce(Closure $fn, mixed $initial): mixed
121 {
122 return array_reduce($this->items, $fn, $initial);
123 }
124
125 /**
126 * @return array<int, T>
127 */
128 public function toArray(): array
129 {
130 return $this->items;
131 }
132
133 /**
134 * @param Closure(T): bool $fn
135 */
136 public function any(Closure $fn): bool
137 {
138 foreach ($this->items as $item) {
139 if ($fn($item)) {
140 return true;
141 }
142 }
143 return false;
144 }
145
146 /**
147 * @param Closure(T): bool $fn
148 */
149 public function none(Closure $fn): bool
150 {
151 foreach ($this->items as $item) {
152 if ($fn($item)) {
153 return false;
154 }
155 }
156 return true;
157 }
158
159 /**
160 * @param Closure(T): bool $fn
161 */
162 public function all(Closure $fn): bool
163 {
164 foreach ($this->items as $item) {
165 if (!$fn($item)) {
166 return false;
167 }
168 }
169 return true;
170 }
171
172 /**
173 * @param Closure(T): bool $fn
174 * @return T|null
175 */
176 public function find(Closure $fn): mixed
177 {
178 foreach ($this->items as $item) {
179 if ($fn($item)) {
180 return $item;
181 }
182 }
183 return null;
184 }
185
186 /**
187 * @return Traversable<int, T>
188 */
189 public function getIterator(): Traversable
190 {
191 return new ArrayIterator($this->items);
192 }
193
194 public function count(): int
195 {
196 return count($this->items);
197 }
198
199 /**
200 * @return array<int, T>
201 */
202 public function jsonSerialize(): array
203 {
204 return $this->items;
205 }
206 }
207