Completed
Push — master ( 54374a...868ce1 )
by Chris
02:36
created

ArrayNode::offsetSet()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 2
crap 12
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\EmptySubjectNodeListException;
6
use DaveRandom\Jom\Exceptions\InvalidKeyException;
7
use DaveRandom\Jom\Exceptions\InvalidReferenceNodeException;
8
use DaveRandom\Jom\Exceptions\WriteOperationForbiddenException;
9
use DaveRandom\Jom\Exceptions\InvalidSubjectNodeException;
10
11
final class ArrayNode extends VectorNode
12
{
13 17
    private function incrementKeys(?Node $current, int $amount = 1): void
14
    {
15 17
        while ($current !== null) {
16 14
            $current->key += $amount;
17 14
            $this->children[$current->key] = $current;
18 14
            $current = $current->nextSibling;
19
        }
20
    }
21
22 20
    private function decrementKeys(?Node $current): void
23
    {
24 20
        while ($current !== null) {
25 8
            unset($this->children[$current->key]);
26 8
            $this->children[--$current->key] = $current;
27 8
            $current = $current->nextSibling;
28
        }
29
    }
30
31 27
    private function normalizeIndex($index): int
32
    {
33 27
        if (!\is_int($index) && !\ctype_digit($index)) {
34
            throw new \TypeError('Index must be an integer');
35
        }
36
37 27
        return (int)$index;
38
    }
39
40
    /**
41
     * @throws EmptySubjectNodeListException
42
     * @throws InvalidKeyException
43
     * @throws InvalidReferenceNodeException
44
     * @throws InvalidSubjectNodeException
45
     * @throws WriteOperationForbiddenException
46
     */
47
    private function setNodeAtIndex($index, Node $node): void
48
    {
49
        if ($index === null || $this->normalizeIndex($index) === \count($this->children)) {
50
            $this->push($node);
51
            return;
52
        }
53
54
        if (isset($this->children[$index])) {
55
            $this->replaceNode($node, $this->children[$index]);
56
            return;
57
        }
58
59
        throw new InvalidKeyException("Index '{$index}' is outside the bounds of the array");
60
    }
61
62
    /**
63
     * @throws EmptySubjectNodeListException
64
     */
65 115
    private function assertNodeListNotEmpty(array $nodes): void
66
    {
67 115
        if (empty($nodes)) {
68 2
            throw new EmptySubjectNodeListException("List of nodes to add must contain at least one node");
69
        }
70
    }
71
72
    /**
73
     * @throws InvalidSubjectNodeException
74
     */
75 165
    public function __construct(?array $children = [], ?Document $ownerDocument = null)
76
    {
77 165
        parent::__construct($ownerDocument);
78
79
        try {
80 165
            $i = 0;
81
82 165
            foreach ($children ?? [] as $child) {
83 165
                $this->appendNode($child, $i++);
84
            }
85
        } catch (InvalidSubjectNodeException $e) {
86
            throw $e;
87
        //@codeCoverageIgnoreStart
88
        } catch (\Exception $e) {
89
            /** @noinspection PhpInternalEntityUsedInspection */
90
            throw unexpected($e);
91
        }
92
        //@codeCoverageIgnoreEnd
93
    }
94
95
    /**
96
     * @throws InvalidKeyException
97
     */
98 4
    public function item(int $index): Node
99
    {
100 4
        return $this->offsetGet($index);
101
    }
102
103
    /**
104
     * @throws WriteOperationForbiddenException
105
     * @throws InvalidSubjectNodeException
106
     * @throws EmptySubjectNodeListException
107
     */
108 101
    public function push(Node ...$nodes): void
109
    {
110 101
        $this->assertNodeListNotEmpty($nodes);
111
112 100
        foreach ($nodes as $node) {
113 100
            $this->appendNode($node, \count($this->children));
114
        }
115
    }
116
117
    /**
118
     * @throws WriteOperationForbiddenException
119
     */
120 13
    public function pop(): ?Node
121
    {
122 13
        $node = $this->lastChild;
123
124 13
        if ($node === null) {
125 1
            return null;
126
        }
127
128
        try {
129 13
            $this->remove($node);
130 1
        } catch (WriteOperationForbiddenException $e) {
131 1
            throw $e;
132
        //@codeCoverageIgnoreStart
133
        } catch (\Exception $e) {
134
            /** @noinspection PhpInternalEntityUsedInspection */
135
            throw unexpected($e);
136
        }
137
        //@codeCoverageIgnoreEnd
138
139 12
        return $node;
140
    }
141
142
    /**
143
     * @throws WriteOperationForbiddenException
144
     * @throws InvalidSubjectNodeException
145
     * @throws EmptySubjectNodeListException
146
     */
147 15
    public function unshift(Node ...$nodes): void
148
    {
149 15
        $this->assertNodeListNotEmpty($nodes);
150
151
        try {
152 14
            $beforeNode = $this->firstChild;
153
154 14
            foreach ($nodes as $key => $node) {
155 14
                $this->insertNode($node, $key, $beforeNode);
156
            }
157
158 11
            $this->incrementKeys($beforeNode, \count($nodes));
159 6
        } catch (WriteOperationForbiddenException | InvalidSubjectNodeException $e) {
160 6
            throw $e;
161
        //@codeCoverageIgnoreStart
162
        } catch (\Exception $e) {
163
            /** @noinspection PhpInternalEntityUsedInspection */
164
            throw unexpected($e);
165
        }
166
        //@codeCoverageIgnoreEnd
167
    }
168
169
    /**
170
     * @throws WriteOperationForbiddenException
171
     */
172 9
    public function shift(): ?Node
173
    {
174 9
        $node = $this->firstChild;
175
176 9
        if ($node === null) {
177 1
            return null;
178
        }
179
180
        try {
181 9
            $this->remove($node);
182 1
        } catch (WriteOperationForbiddenException $e) {
183 1
            throw $e;
184
        //@codeCoverageIgnoreStart
185
        } catch (\Exception $e) {
186
            /** @noinspection PhpInternalEntityUsedInspection */
187
            throw unexpected($e);
188
        }
189
        //@codeCoverageIgnoreEnd
190
191 8
        return $node;
192
    }
193
194
    /**
195
     * @throws WriteOperationForbiddenException
196
     * @throws InvalidSubjectNodeException
197
     * @throws InvalidReferenceNodeException
198
     */
199 32
    public function insert(Node $node, ?Node $beforeNode): void
200
    {
201 32
        if ($beforeNode === null) {
202 12
            $this->appendNode($node, \count($this->children));
203 6
            return;
204
        }
205
206 20
        $key = $beforeNode !== null
207 20
            ? $beforeNode->key
208 20
            : \count($this->children);
209
210 20
        $this->insertNode($node, $key, $beforeNode);
211
212 6
        $this->incrementKeys($beforeNode);
213
    }
214
215
    /**
216
     * @param Node|int $nodeOrIndex
217
     * @throws InvalidKeyException
218
     * @throws InvalidReferenceNodeException
219
     * @throws InvalidSubjectNodeException
220
     * @throws WriteOperationForbiddenException
221
     */
222 21
    public function replace(Node $newNode, $nodeOrIndex): void
223
    {
224 21
        $this->replaceNode($newNode, $this->resolveNode($nodeOrIndex));
225
    }
226
227
    /**
228
     * @throws WriteOperationForbiddenException
229
     * @throws InvalidSubjectNodeException
230
     */
231 22
    public function remove(Node $node): void
232
    {
233 22
        $next = $node->nextSibling;
234
235 22
        $this->removeNode($node);
236
237 20
        $this->decrementKeys($next);
238
    }
239
240
    /**
241
     * @throws InvalidKeyException
242
     */
243 27
    public function offsetGet($index): Node
244
    {
245 27
        $index = $this->normalizeIndex($index);
246
247 27
        if (!isset($this->children[$index])) {
248 7
            throw new InvalidKeyException("Index '{$index}' is outside the bounds of the array");
249
        }
250
251 26
        return $this->children[$index];
252
    }
253
254
    /**
255
     * @throws WriteOperationForbiddenException
256
     * @throws InvalidSubjectNodeException
257
     * @throws InvalidKeyException
258
     */
259
    public function offsetSet($index, $value): void
260
    {
261
        try {
262
            $this->setNodeAtIndex($index, $value);
263
        } catch (WriteOperationForbiddenException | InvalidSubjectNodeException | InvalidKeyException $e) {
264
            throw $e;
265
        //@codeCoverageIgnoreStart
266
        } catch (\Exception $e) {
267
            /** @noinspection PhpInternalEntityUsedInspection */
268
            throw unexpected($e);
269
        }
270
        //@codeCoverageIgnoreEnd
271
    }
272
273
    public function getValue(): array
274
    {
275
        $result = [];
276
277
        foreach ($this as $value) {
278
            $result[] = $value->getValue();
279
        }
280
281
        return $result;
282
    }
283
284
    public function toArray(): array
285
    {
286
        return $this->getValue();
287
    }
288
}
289