Completed
Push — master ( 9afc27...035c9e )
by Chris
04:26
created

ArrayNode   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Test Coverage

Coverage 70.97%

Importance

Changes 0
Metric Value
wmc 44
dl 0
loc 258
ccs 66
cts 93
cp 0.7097
rs 8.3396
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A decrementKeys() 0 6 2
A incrementKeys() 0 6 2
A shift() 0 19 4
B offsetSet() 0 28 6
A item() 0 3 1
A remove() 0 7 1
A insert() 0 14 3
A replace() 0 3 1
A toArray() 0 3 1
A getValue() 0 9 2
A offsetGet() 0 7 2
A normalizeIndex() 0 7 3
A __construct() 0 15 4
A pop() 0 19 4
A push() 0 8 3
B unshift() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like ArrayNode often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ArrayNode, and based on these observations, apply Extract Interface, too.

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