Completed
Push — master ( f400d6...2675c1 )
by Chris
02:17
created

ArrayNode   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 239
Duplicated Lines 0 %

Test Coverage

Coverage 55.45%

Importance

Changes 0
Metric Value
wmc 43
dl 0
loc 239
c 0
b 0
f 0
ccs 56
cts 101
cp 0.5545
rs 8.3157

14 Methods

Rating   Name   Duplication   Size   Complexity  
A decrementKeys() 0 6 2
A shift() 0 17 4
D offsetSet() 0 34 9
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 __construct() 0 14 4
A incrementKeys() 0 6 2
A pop() 0 17 4
B unshift() 0 18 5
A push() 0 8 3

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