Skip to content

Commit 7ef80d4

Browse files
author
nejc
committed
Implement DataTypeInterface methods in TypeSafeArray and fix setValue signature
1 parent 29978d7 commit 7ef80d4

File tree

1 file changed

+268
-0
lines changed

1 file changed

+268
-0
lines changed
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)