Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
15 / 15
CRAP
100.00% covered (success)
100.00%
1 / 1
Collection
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
15 / 15
26
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateExistingKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateNewKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getElement
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getElements
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getKey
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getKeys
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 update
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 delete
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * @author: Doug Wilbourne (dougwilbourne@gmail.com)
5 */
6
7declare(strict_types=1);
8
9namespace pvc\struct\collection;
10
11use ArrayIterator;
12use IteratorIterator;
13use pvc\interfaces\struct\collection\CollectionInterface;
14use pvc\struct\collection\err\DuplicateKeyException;
15use pvc\struct\collection\err\InvalidKeyException;
16use pvc\struct\collection\err\NonExistentKeyException;
17
18/**
19 * Class Collection
20 * @template ElementType
21 * @extends IteratorIterator<non-negative-int, ElementType, ArrayIterator>
22 * @implements CollectionInterface<ElementType>
23 *
24 * elements in a collection cannot be null
25 */
26class Collection extends IteratorIterator implements CollectionInterface
27{
28    /**
29     * @var ArrayIterator<int, ElementType>
30     */
31    protected ArrayIterator $iterator;
32
33    /**
34     * @var callable(ElementType, ElementType):int|null
35     * used by getElements to return the elements in whatever sort order you choose
36     */
37    protected $comparator = null;
38
39    /**
40     * @param array<non-negative-int, ElementType> $elements
41     * @param callable(ElementType, ElementType):int|null $comparator
42     */
43    public function __construct(array $elements = [], callable|null $comparator = null)
44    {
45        $this->iterator = new ArrayIterator($elements);
46        $this->comparator = $comparator;
47        parent::__construct($this->iterator);
48    }
49
50    /**
51     * count
52     * @return non-negative-int
53     */
54    public function count(): int
55    {
56        return $this->iterator->count();
57    }
58
59    /**
60     * isEmpty returns whether the collection is empty or not
61     * @return bool
62     */
63    public function isEmpty(): bool
64    {
65        return (0 == $this->count());
66    }
67
68    /**
69     * validateKey encapsulates the logic that all keys must be non-negative integers
70     * @param int $key
71     * @return bool
72     */
73    protected function validateKey(int $key): bool
74    {
75        return $key >= 0;
76    }
77
78    /**
79     * validateExistingKey ensures that the key is both valid and exists in the collection
80     * @param int $key
81     * @throws InvalidKeyException
82     * @throws NonExistentKeyException
83     */
84    public function validateExistingKey(int $key): bool
85    {
86        return $this->iterator->offsetExists($key);
87    }
88
89    /**
90     * validateNewKey ensures that the key does not exist in the collection
91     * @param int $key
92     */
93    public function validateNewKey(int $key): bool
94    {
95        return !$this->iterator->offsetExists($key);
96    }
97
98    /**
99     * getElement
100     * @param non-negative-int $key
101     * @return ElementType
102     */
103    public function getElement(int $key): mixed
104    {
105        if (!$this->validateKey($key)) {
106            throw new InvalidKeyException($key);
107        }
108        if (!$this->validateExistingKey($key)) {
109            throw new NonExistentKeyException($key);
110        }
111        /**
112         * element cannot be null
113         */
114        $element = $this->iterator->offsetGet($key);
115        assert(!is_null($element));
116        return $element;
117    }
118
119    /**
120     * now implement methods explicitly defined in the interface
121     */
122
123    /**
124     * @function getElements
125     * @return array<ElementType>
126     */
127    public function getElements(): array
128    {
129        if ($this->comparator) {
130            $this->iterator->uasort($this->comparator);
131        }
132        return iterator_to_array($this->iterator);
133    }
134
135    /**
136     * @function getKey
137     * @param ElementType $element
138     * @param bool $strict
139     * @return non-negative-int|false
140     */
141    public function getKey($element, bool $strict = true): int|false
142    {
143        $key = array_search($element, $this->getElements(), $strict);
144        assert((is_int($key) && ($key >= 0)) || $key === false);
145        return $key;
146    }
147
148    /**
149     * getKeys returns all the keys in the collection where the corresponding element equals $element.
150     *
151     * @param ElementType $element
152     * @param bool $strict
153     * @return array<non-negative-int>
154     */
155    public function getKeys($element, bool $strict = true): array
156    {
157        /** @var array<non-negative-int> $keys */
158        $keys = array_keys($this->getElements(), $element, $strict);
159        return $keys;
160    }
161
162    /**
163     * add
164     *
165     * Unlike when you are dealing with a raw array, using an existing key will throw an exception instead
166     * of overwriting an existing entry in the array.  Use update to be explicit about updating an entry.
167     *
168     * @param non-negative-int $key
169     * @param ElementType $element
170     * @throws DuplicateKeyException
171     * @throws InvalidKeyException
172     */
173    public function add(int $key, $element): void
174    {
175        if (!$this->validateKey($key)) {
176            throw new InvalidKeyException($key);
177        }
178        if (!$this->validateNewKey($key)) {
179            throw new DuplicateKeyException($key);
180        }
181        $this->iterator->offsetSet($key, $element);
182    }
183
184    /**
185     * update assigns a new element to the entry with index $key
186     *
187     * @param non-negative-int $key
188     * @param ElementType $element
189     * @throws InvalidKeyException
190     * @throws NonExistentKeyException
191     */
192    public function update(int $key, $element): void
193    {
194        if (!$this->validateKey($key)) {
195            throw new InvalidKeyException($key);
196        }
197        if (!$this->validateExistingKey($key)) {
198            throw new NonExistentKeyException($key);
199        }
200        $this->iterator->offsetSet($key, $element);
201    }
202
203    /**
204     * delete removes an element from the collection.  Unlike unset, this operation throws an exception if the
205     * key does not exist.
206     *
207     * @param non-negative-int $key
208     * @return void
209     * @throws InvalidKeyException
210     * @throws NonExistentKeyException
211     */
212    public function delete(int $key): void
213    {
214        if (!$this->validateKey($key)) {
215            throw new InvalidKeyException($key);
216        }
217        if (!$this->validateExistingKey($key)) {
218            throw new NonExistentKeyException($key);
219        }
220        $this->iterator->offsetUnset($key);
221    }
222
223    /**
224     * @param non-negative-int $key
225     * @return int
226     */
227    public function getIndex(int $key): int
228    {
229        return -1;
230    }
231
232    public function setIndex(int $key, int $newIndex): void
233    {
234    }
235}