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