Skip to content

Commit f4bd3af

Browse files
author
nejc
committed
Implement DynamicArray with capacity management, fix FixedSizeArray warning, and update array tests
1 parent c5e02a2 commit f4bd3af

File tree

4 files changed

+442
-34
lines changed

4 files changed

+442
-34
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Composite\Arrays;
6+
7+
use Nejcc\PhpDatatypes\Composite\Arrays\DynamicArray;
8+
use Nejcc\PhpDatatypes\Exceptions\InvalidArgumentException;
9+
use Nejcc\PhpDatatypes\Exceptions\TypeMismatchException;
10+
use PHPUnit\Framework\TestCase;
11+
12+
final class DynamicArrayTest extends TestCase
13+
{
14+
public function testCreateDynamicArray()
15+
{
16+
$array = new DynamicArray(\stdClass::class, 4);
17+
$this->assertEquals(4, $array->getCapacity());
18+
$this->assertEquals(0, count($array));
19+
}
20+
21+
public function testCreateWithInvalidCapacity()
22+
{
23+
$this->expectException(InvalidArgumentException::class);
24+
new DynamicArray(\stdClass::class, 0);
25+
}
26+
27+
public function testCreateWithInitialData()
28+
{
29+
$obj1 = new \stdClass();
30+
$obj2 = new \stdClass();
31+
$array = new DynamicArray(\stdClass::class, 2, [$obj1, $obj2]);
32+
$this->assertEquals(2, count($array));
33+
$this->assertEquals(2, $array->getCapacity());
34+
}
35+
36+
public function testCreateWithExcessiveInitialData()
37+
{
38+
$obj1 = new \stdClass();
39+
$obj2 = new \stdClass();
40+
$obj3 = new \stdClass();
41+
$array = new DynamicArray(\stdClass::class, 2, [$obj1, $obj2, $obj3]);
42+
$this->assertEquals(3, count($array));
43+
$this->assertEquals(3, $array->getCapacity());
44+
}
45+
46+
public function testReserveCapacity()
47+
{
48+
$array = new DynamicArray(\stdClass::class, 2);
49+
$array->reserve(10);
50+
$this->assertEquals(10, $array->getCapacity());
51+
$array->reserve(5); // Should not decrease
52+
$this->assertEquals(10, $array->getCapacity());
53+
}
54+
55+
public function testShrinkToFit()
56+
{
57+
$array = new DynamicArray(\stdClass::class, 10);
58+
$obj1 = new \stdClass();
59+
$obj2 = new \stdClass();
60+
$array[] = $obj1;
61+
$array[] = $obj2;
62+
$this->assertEquals(10, $array->getCapacity());
63+
$array->shrinkToFit();
64+
$this->assertEquals(2, $array->getCapacity());
65+
}
66+
67+
public function testDynamicResizingOnAppend()
68+
{
69+
$array = new DynamicArray(\stdClass::class, 2);
70+
$obj1 = new \stdClass();
71+
$obj2 = new \stdClass();
72+
$obj3 = new \stdClass();
73+
$array[] = $obj1;
74+
$array[] = $obj2;
75+
$this->assertEquals(2, $array->getCapacity());
76+
$array[] = $obj3;
77+
$this->assertEquals(4, $array->getCapacity());
78+
$this->assertEquals(3, count($array));
79+
}
80+
81+
public function testDynamicResizingOnOffsetSet()
82+
{
83+
$array = new DynamicArray(\stdClass::class, 2);
84+
$obj = new \stdClass();
85+
$array[5] = $obj;
86+
$this->assertEquals(6, $array->getCapacity());
87+
$this->assertSame($obj, $array[5]);
88+
}
89+
90+
public function testSetInvalidType()
91+
{
92+
$array = new DynamicArray(\stdClass::class, 2);
93+
$this->expectException(TypeMismatchException::class);
94+
$array[] = "not an object";
95+
}
96+
97+
public function testSetValueAdjustsCapacity()
98+
{
99+
$array = new DynamicArray(\stdClass::class, 2);
100+
$obj1 = new \stdClass();
101+
$obj2 = new \stdClass();
102+
$obj3 = new \stdClass();
103+
$array->setValue([$obj1, $obj2, $obj3]);
104+
$this->assertEquals(3, $array->getCapacity());
105+
$this->assertEquals(3, count($array));
106+
}
107+
108+
public function testIteration()
109+
{
110+
$array = new DynamicArray(\stdClass::class, 2);
111+
$obj1 = new \stdClass();
112+
$obj2 = new \stdClass();
113+
$array[] = $obj1;
114+
$array[] = $obj2;
115+
$elements = [];
116+
foreach ($array as $element) {
117+
$elements[] = $element;
118+
}
119+
$this->assertCount(2, $elements);
120+
$this->assertSame($obj1, $elements[0]);
121+
$this->assertSame($obj2, $elements[1]);
122+
}
123+
}

src/Composite/Arrays/DynamicArray.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Nejcc\PhpDatatypes\Composite\Arrays;
6+
7+
use Nejcc\PhpDatatypes\Exceptions\InvalidArgumentException;
8+
use Nejcc\PhpDatatypes\Exceptions\TypeMismatchException;
9+
10+
/**
11+
* DynamicArray - A type-safe array with dynamic capacity management.
12+
*/
13+
final class DynamicArray extends TypeSafeArray
14+
{
15+
/**
16+
* @var int Current capacity of the array
17+
*/
18+
private int $capacity;
19+
20+
/**
21+
* Create a new DynamicArray instance
22+
*
23+
* @param string $elementType The type that all elements must conform to
24+
* @param int $initialCapacity The initial capacity of the array
25+
* @param array $initialData Optional initial data
26+
*
27+
* @throws InvalidArgumentException If elementType is invalid or capacity is non-positive
28+
* @throws TypeMismatchException If initial data contains invalid types
29+
*/
30+
public function __construct(string $elementType, int $initialCapacity = 8, array $initialData = [])
31+
{
32+
if ($initialCapacity <= 0) {
33+
throw new InvalidArgumentException('Capacity must be a positive integer');
34+
}
35+
$this->capacity = $initialCapacity;
36+
parent::__construct($elementType, $initialData);
37+
if (count($initialData) > $this->capacity) {
38+
$this->capacity = count($initialData);
39+
}
40+
}
41+
42+
/**
43+
* Get the current capacity
44+
*
45+
* @return int
46+
*/
47+
public function getCapacity(): int
48+
{
49+
return $this->capacity;
50+
}
51+
52+
/**
53+
* Reserve capacity for at least $capacity elements
54+
*
55+
* @param int $capacity
56+
*
57+
* @return void
58+
*/
59+
public function reserve(int $capacity): void
60+
{
61+
if ($capacity > $this->capacity) {
62+
$this->capacity = $capacity;
63+
}
64+
}
65+
66+
/**
67+
* Shrink the capacity to fit the current number of elements
68+
*
69+
* @return void
70+
*/
71+
public function shrinkToFit(): void
72+
{
73+
$this->capacity = count($this->getValue());
74+
}
75+
76+
/**
77+
* ArrayAccess implementation (override to grow capacity as needed)
78+
*/
79+
public function offsetSet($offset, $value): void
80+
{
81+
if (!$this->isValidType($value)) {
82+
throw new TypeMismatchException(
83+
"Value must be of type {$this->getElementType()}"
84+
);
85+
}
86+
if (is_null($offset)) {
87+
// Appending
88+
if (count($this->getValue()) >= $this->capacity) {
89+
$this->capacity = max(1, $this->capacity * 2);
90+
}
91+
} else {
92+
if ($offset >= $this->capacity) {
93+
$this->capacity = $offset + 1;
94+
}
95+
}
96+
parent::offsetSet($offset, $value);
97+
}
98+
99+
/**
100+
* Set the array value (override to adjust capacity)
101+
*
102+
* @param mixed $value
103+
*
104+
* @throws TypeMismatchException
105+
*/
106+
public function setValue(mixed $value): void
107+
{
108+
if (!is_array($value)) {
109+
throw new TypeMismatchException('Value must be an array.');
110+
}
111+
if (count($value) > $this->capacity) {
112+
$this->capacity = count($value);
113+
}
114+
parent::setValue($value);
115+
}
116+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Nejcc\PhpDatatypes\Composite\Arrays;
6+
7+
use Nejcc\PhpDatatypes\Exceptions\InvalidArgumentException;
8+
use Nejcc\PhpDatatypes\Exceptions\TypeMismatchException;
9+
10+
/**
11+
* FixedSizeArray - A type-safe array implementation that enforces both type constraints
12+
* and a fixed size on array elements.
13+
*/
14+
final class FixedSizeArray extends TypeSafeArray
15+
{
16+
/**
17+
* @var int The fixed size of the array
18+
*/
19+
private int $size;
20+
21+
/**
22+
* Create a new FixedSizeArray instance
23+
*
24+
* @param string $elementType The type that all elements must conform to
25+
* @param int $size The fixed size of the array
26+
* @param array $initialData Optional initial data
27+
*
28+
* @throws InvalidArgumentException If elementType is invalid or size is non-positive
29+
* @throws TypeMismatchException If initial data contains invalid types or exceeds size
30+
*/
31+
public function __construct(string $elementType, int $size, array $initialData = [])
32+
{
33+
if ($size <= 0) {
34+
throw new InvalidArgumentException('Size must be a positive integer');
35+
}
36+
37+
if (count($initialData) > $size) {
38+
throw new InvalidArgumentException(
39+
"Initial data size ({$size}) exceeds fixed size ({$size})"
40+
);
41+
}
42+
43+
$this->size = $size;
44+
parent::__construct($elementType, $initialData);
45+
}
46+
47+
/**
48+
* Get the fixed size of the array
49+
*
50+
* @return int The fixed size
51+
*/
52+
public function getSize(): int
53+
{
54+
return $this->size;
55+
}
56+
57+
/**
58+
* Check if the array is full
59+
*
60+
* @return bool True if the array is at its maximum size
61+
*/
62+
public function isFull(): bool
63+
{
64+
return count($this->getValue()) >= $this->size;
65+
}
66+
67+
/**
68+
* Check if the array is empty
69+
*
70+
* @return bool True if the array has no elements
71+
*/
72+
public function isEmpty(): bool
73+
{
74+
return count($this->getValue()) === 0;
75+
}
76+
77+
/**
78+
* Get the number of remaining slots
79+
*
80+
* @return int The number of available slots
81+
*/
82+
public function getRemainingSlots(): int
83+
{
84+
return $this->size - count($this->getValue());
85+
}
86+
87+
/**
88+
* ArrayAccess implementation
89+
*/
90+
public function offsetSet($offset, $value): void
91+
{
92+
if (is_null($offset) && $this->isFull()) {
93+
throw new InvalidArgumentException('Array is at maximum capacity');
94+
}
95+
96+
if (!is_null($offset) && $offset >= $this->size) {
97+
throw new InvalidArgumentException(
98+
"Index {$offset} is out of bounds (size: {$this->size})"
99+
);
100+
}
101+
102+
parent::offsetSet($offset, $value);
103+
}
104+
105+
/**
106+
* Set the array value
107+
*
108+
* @param mixed $value The new array data
109+
*
110+
* @throws TypeMismatchException If any element doesn't match the required type
111+
* @throws InvalidArgumentException If the new array size exceeds the fixed size
112+
*/
113+
public function setValue(mixed $value): void
114+
{
115+
if (!is_array($value)) {
116+
throw new TypeMismatchException('Value must be an array');
117+
}
118+
119+
if (count($value) > $this->size) {
120+
throw new InvalidArgumentException(
121+
"New array size (" . count($value) . ") exceeds fixed size ({$this->size})"
122+
);
123+
}
124+
125+
parent::setValue($value);
126+
}
127+
128+
/**
129+
* Fill the array with a value up to its capacity
130+
*
131+
* @param mixed $value The value to fill with
132+
*
133+
* @return self
134+
*
135+
* @throws TypeMismatchException If the value doesn't match the required type
136+
*/
137+
public function fill($value): self
138+
{
139+
if (!$this->isValidType($value)) {
140+
throw new TypeMismatchException(
141+
"Value must be of type {$this->getElementType()}"
142+
);
143+
}
144+
145+
$this->setValue(array_fill(0, $this->size, $value));
146+
return $this;
147+
}
148+
149+
/**
150+
* Create a new array with the same type and size
151+
*
152+
* @return self A new empty array with the same constraints
153+
*/
154+
public function createEmpty(): self
155+
{
156+
return new self($this->getElementType(), $this->size);
157+
}
158+
}

0 commit comments

Comments
 (0)