1
+ <?php
2
+
3
+ namespace Nejcc \PhpDatatypes \Composite \Arrays ;
4
+
5
+ use Nejcc \PhpDatatypes \Interfaces \DataTypeInterface ;
6
+ use Nejcc \PhpDatatypes \Exceptions \InvalidArgumentException ;
7
+ use Nejcc \PhpDatatypes \Exceptions \TypeMismatchException ;
8
+
9
+ /**
10
+ * TypeSafeArray - A type-safe array implementation that enforces type constraints
11
+ * on array elements.
12
+ */
13
+ class TypeSafeArray implements DataTypeInterface, \ArrayAccess, \Countable, \Iterator
14
+ {
15
+ /**
16
+ * @var array The internal array storage
17
+ */
18
+ private array $ data ;
19
+
20
+ /**
21
+ * @var string The type that all elements must conform to
22
+ */
23
+ private string $ elementType ;
24
+
25
+ /**
26
+ * @var int Current position for Iterator implementation
27
+ */
28
+ private int $ position = 0 ;
29
+
30
+ /**
31
+ * Create a new TypeSafeArray instance
32
+ *
33
+ * @param string $elementType The type that all elements must conform to
34
+ * @param array $initialData Optional initial data
35
+ * @throws InvalidArgumentException If elementType is invalid
36
+ * @throws TypeMismatchException If initial data contains invalid types
37
+ */
38
+ public function __construct (string $ elementType , array $ initialData = [])
39
+ {
40
+ if (!class_exists ($ elementType ) && !interface_exists ($ elementType )) {
41
+ throw new InvalidArgumentException ("Invalid element type: {$ elementType }" );
42
+ }
43
+
44
+ $ this ->elementType = $ elementType ;
45
+ $ this ->data = [];
46
+
47
+ if (!empty ($ initialData )) {
48
+ $ this ->validateArray ($ initialData );
49
+ $ this ->data = $ initialData ;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Validate that all elements in an array match the required type
55
+ *
56
+ * @param array $data The array to validate
57
+ * @throws TypeMismatchException If any element doesn't match the required type
58
+ */
59
+ private function validateArray (array $ data ): void
60
+ {
61
+ foreach ($ data as $ key => $ value ) {
62
+ if (!$ this ->isValidType ($ value )) {
63
+ throw new TypeMismatchException (
64
+ "Element at key ' {$ key }' must be of type {$ this ->elementType }"
65
+ );
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if a value matches the required type
72
+ *
73
+ * @param mixed $value The value to check
74
+ * @return bool True if the value matches the required type
75
+ */
76
+ private function isValidType ($ value ): bool
77
+ {
78
+ return $ value instanceof $ this ->elementType ;
79
+ }
80
+
81
+ /**
82
+ * Get the type of elements this array accepts
83
+ *
84
+ * @return string The element type
85
+ */
86
+ public function getElementType (): string
87
+ {
88
+ return $ this ->elementType ;
89
+ }
90
+
91
+ /**
92
+ * Get all elements in the array
93
+ *
94
+ * @return array The array elements
95
+ */
96
+ public function toArray (): array
97
+ {
98
+ return $ this ->data ;
99
+ }
100
+
101
+ /**
102
+ * ArrayAccess implementation
103
+ */
104
+ public function offsetExists ($ offset ): bool
105
+ {
106
+ return isset ($ this ->data [$ offset ]);
107
+ }
108
+
109
+ public function offsetGet ($ offset ): mixed
110
+ {
111
+ return $ this ->data [$ offset ] ?? null ;
112
+ }
113
+
114
+ public function offsetSet ($ offset , $ value ): void
115
+ {
116
+ if (!$ this ->isValidType ($ value )) {
117
+ throw new TypeMismatchException (
118
+ "Value must be of type {$ this ->elementType }"
119
+ );
120
+ }
121
+
122
+ if (is_null ($ offset )) {
123
+ $ this ->data [] = $ value ;
124
+ } else {
125
+ $ this ->data [$ offset ] = $ value ;
126
+ }
127
+ }
128
+
129
+ public function offsetUnset ($ offset ): void
130
+ {
131
+ unset($ this ->data [$ offset ]);
132
+ }
133
+
134
+ /**
135
+ * Countable implementation
136
+ */
137
+ public function count (): int
138
+ {
139
+ return count ($ this ->data );
140
+ }
141
+
142
+ /**
143
+ * Iterator implementation
144
+ */
145
+ public function current (): mixed
146
+ {
147
+ return $ this ->data [$ this ->position ];
148
+ }
149
+
150
+ public function key (): mixed
151
+ {
152
+ return $ this ->position ;
153
+ }
154
+
155
+ public function next (): void
156
+ {
157
+ ++$ this ->position ;
158
+ }
159
+
160
+ public function rewind (): void
161
+ {
162
+ $ this ->position = 0 ;
163
+ }
164
+
165
+ public function valid (): bool
166
+ {
167
+ return isset ($ this ->data [$ this ->position ]);
168
+ }
169
+
170
+ /**
171
+ * Map operation - apply a callback to each element
172
+ *
173
+ * @param callable $callback The callback to apply
174
+ * @return TypeSafeArray A new array with the mapped values
175
+ * @throws TypeMismatchException If the callback returns invalid types
176
+ */
177
+ public function map (callable $ callback ): self
178
+ {
179
+ $ result = new self ($ this ->elementType );
180
+ foreach ($ this ->data as $ key => $ value ) {
181
+ $ result [$ key ] = $ callback ($ value , $ key );
182
+ }
183
+ return $ result ;
184
+ }
185
+
186
+ /**
187
+ * Filter operation - filter elements based on a callback
188
+ *
189
+ * @param callable $callback The callback to use for filtering
190
+ * @return TypeSafeArray A new array with the filtered values
191
+ */
192
+ public function filter (callable $ callback ): self
193
+ {
194
+ $ result = new self ($ this ->elementType );
195
+ foreach ($ this ->data as $ key => $ value ) {
196
+ if ($ callback ($ value , $ key )) {
197
+ $ result [$ key ] = $ value ;
198
+ }
199
+ }
200
+ return $ result ;
201
+ }
202
+
203
+ /**
204
+ * Reduce operation - reduce the array to a single value
205
+ *
206
+ * @param callable $callback The callback to use for reduction
207
+ * @param mixed $initial The initial value
208
+ * @return mixed The reduced value
209
+ */
210
+ public function reduce (callable $ callback , $ initial = null )
211
+ {
212
+ return array_reduce ($ this ->data , $ callback , $ initial );
213
+ }
214
+
215
+ /**
216
+ * String representation of the array
217
+ *
218
+ * @return string
219
+ */
220
+ public function __toString (): string
221
+ {
222
+ return json_encode ($ this ->data );
223
+ }
224
+
225
+ /**
226
+ * Get the array value
227
+ *
228
+ * @return array The array data
229
+ */
230
+ public function getValue (): array
231
+ {
232
+ return $ this ->data ;
233
+ }
234
+
235
+ /**
236
+ * Set the array value
237
+ *
238
+ * @param mixed $value The new array data
239
+ * @throws TypeMismatchException If any element doesn't match the required type
240
+ */
241
+ public function setValue (mixed $ value ): void
242
+ {
243
+ if (!is_array ($ value )) {
244
+ throw new TypeMismatchException ('Value must be an array. ' );
245
+ }
246
+ $ this ->validateArray ($ value );
247
+ $ this ->data = $ value ;
248
+ }
249
+
250
+ /**
251
+ * Check if this array equals another array
252
+ *
253
+ * @param DataTypeInterface $other The other array to compare with
254
+ * @return bool True if the arrays are equal
255
+ */
256
+ public function equals (DataTypeInterface $ other ): bool
257
+ {
258
+ if (!$ other instanceof self) {
259
+ return false ;
260
+ }
261
+
262
+ if ($ this ->elementType !== $ other ->elementType ) {
263
+ return false ;
264
+ }
265
+
266
+ return $ this ->data === $ other ->data ;
267
+ }
268
+ }
0 commit comments