Completed
Push — master ( 5d9a5d...e6d18b )
by Chris
02:53
created

ArrayNode::offsetSet()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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