Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.15% covered (success)
96.15%
50 / 52
86.67% covered (warning)
86.67%
13 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ItemHashmap
96.15% covered (success)
96.15%
50 / 52
86.67% covered (warning)
86.67%
13 / 15
25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 toImmutable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 toMutable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 first
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 offsetCheck
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 typeCheck
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 offsetSet
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 offsetUnset
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
1<?php
2namespace Apie\Core\Lists;
3
4use Apie\Core\Exceptions\IndexNotFoundException;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Exceptions\ObjectIsEmpty;
7use Apie\Core\Exceptions\ObjectIsImmutable;
8use Apie\Core\TypeUtils;
9use Apie\Core\ValueObjects\Utils;
10use ArrayIterator;
11use Iterator;
12use ReflectionClass;
13use ReflectionType;
14use stdClass;
15
16/**
17 * @template T
18 * @implements HashmapInterface<T>
19 */
20class ItemHashmap implements HashmapInterface
21{
22    protected stdClass $internal;
23    /**
24     * @var array<string|int, T>
25     */
26    protected array $internalArray = [];
27
28    /** @var ReflectionType[] */
29    private static $typeMapping = [];
30
31    protected bool $mutable = true;
32
33    /**
34     * @param array<string|int, T>|stdClass $input
35     */
36    final public function __construct(array|stdClass $input = [])
37    {
38        $this->internal = new stdClass();
39        $oldMutable = $this->mutable;
40        $this->mutable = true;
41        foreach ($input as $key => $item) {
42            $this->offsetSet($key, $item);
43        }
44        $this->mutable = $oldMutable;
45    }
46    
47    final public function toImmutable(): static
48    {
49        $copy = clone $this;
50        $copy->internal = clone $copy->internal;
51        $copy->mutable = false;
52        return $copy;
53    }
54
55    final public function toMutable(): static
56    {
57        $copy = clone $this;
58        $copy->internal = clone $copy->internal;
59        $copy->mutable = true;
60        return $copy;
61    }
62    
63    /**
64     * @return T
65     */
66    public function first(): mixed
67    {
68        if (empty($this->internalArray)) {
69            throw ObjectIsEmpty::createForHashmap();
70        }
71        return reset($this->internalArray);
72    }
73
74    public function count(): int
75    {
76        return count($this->internalArray);
77    }
78
79    /**
80     * @return array<string|int, T>
81     */
82    public function toArray(): array
83    {
84        return $this->internalArray;
85    }
86
87    /**
88     * @return Iterator<string|int, T>
89     */
90    public function getIterator(): Iterator
91    {
92        return new ArrayIterator($this->internalArray);
93    }
94
95    /**
96     * @return stdClass
97     */
98    public function jsonSerialize(): stdClass
99    {
100        return $this->internal;
101    }
102
103    public function offsetExists(mixed $offset): bool
104    {
105        return array_key_exists($offset, $this->internalArray);
106    }
107
108    public function offsetGet(mixed $offset): mixed
109    {
110        $offset = Utils::toString($offset);
111        if (!array_key_exists($offset, $this->internalArray)) {
112            throw new IndexNotFoundException($offset);
113        }
114        return $this->internalArray[$offset];
115    }
116
117    protected function getType(): ReflectionType
118    {
119        $currentClass = static::class;
120        if (!isset(self::$typeMapping[$currentClass])) {
121            self::$typeMapping[$currentClass] = (new ReflectionClass($currentClass))->getMethod('offsetGet')->getReturnType();
122        }
123        return self::$typeMapping[$currentClass];
124    }
125
126    protected function offsetCheck(mixed $value): string
127    {
128        if ($value === null) { // append
129            return (string) count($this->internalArray);
130        }
131        return Utils::toString($value);
132    }
133
134    protected function typeCheck(mixed $value): void
135    {
136        if (static::class === ItemHashmap::class) {
137            return;
138        }
139        $type = $this->getType();
140        if (!TypeUtils::matchesType($type, $value)) {
141            throw new InvalidTypeException($value, $type->__toString());
142        }
143    }
144    
145    public function offsetSet(mixed $offset, mixed $value): void
146    {
147        if (!$this->mutable) {
148            throw new ObjectIsImmutable($this);
149        }
150        $offset = $this->offsetCheck($offset);
151        $this->typeCheck($value);
152        $this->internal->{$offset} = $value;
153        $this->internalArray[$offset] = $value;
154    }
155
156    public function offsetUnset(mixed $offset): void
157    {
158        if (!$this->mutable) {
159            throw new ObjectIsImmutable($this);
160        }
161        $offset = Utils::toString($offset);
162        if (array_key_exists($offset, $this->internalArray)) {
163            unset($this->internalArray[$offset]);
164            unset($this->internal->{$offset});
165            return;
166        }
167        
168        throw new IndexNotFoundException($offset);
169    }
170}