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

VectorNode::clear()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
ccs 0
cts 5
cp 0
rs 9.4285
cc 3
eloc 5
nc 3
nop 0
crap 12
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\InvalidKeyException;
6
use DaveRandom\Jom\Exceptions\InvalidReferenceNodeException;
7
use DaveRandom\Jom\Exceptions\InvalidSubjectNodeException;
8
use DaveRandom\Jom\Exceptions\WriteOperationForbiddenException;
9
10
abstract class VectorNode extends Node implements \Countable, \IteratorAggregate, \ArrayAccess
11
{
12
    /** @var Node|null */
13
    protected $firstChild;
14
15
    /** @var Node|null */
16
    protected $lastChild;
17
18
    /** @var Node[] */
19
    protected $children = [];
20
21
    protected $activeIteratorCount = 0;
22
23
    /**
24
     * @throws InvalidKeyException
25
     */
26
    protected function resolveNode($nodeOrKey): Node
27
    {
28
        if ($nodeOrKey instanceof Node) {
29
            return $nodeOrKey;
30
        }
31
32
        if (isset($this->children[$nodeOrKey])) {
33
            return $this->children[$nodeOrKey];
34
        }
35
36
        throw new InvalidKeyException("{$nodeOrKey} does not reference a valid child node");
37
    }
38
39
    /**
40
     * @throws WriteOperationForbiddenException
41
     * @throws InvalidSubjectNodeException
42
     */
43 66
    protected function appendNode(Node $node, $key): Node
44
    {
45 66
        if ($this->activeIteratorCount !== 0) {
46 1
            throw new WriteOperationForbiddenException('Cannot modify a vector with an active iterator');
47
        }
48
49 66
        if ($node->ownerDocument !== $this->ownerDocument) {
50 8
            throw new InvalidSubjectNodeException('Node belongs to a different document');
51
        }
52
53 59
        if ($node->parent !== null) {
54 5
            throw new InvalidSubjectNodeException('Node already present in the document');
55
        }
56
57 59
        $node->parent = $this;
58 59
        $node->key = $key;
59 59
        $this->children[$key] = $node;
60
61 59
        $previous = $this->lastChild;
62
63 59
        $this->lastChild = $node;
64 59
        $this->firstChild = $this->firstChild ?? $node;
65
66 59
        $node->previousSibling = $previous;
67
68 59
        if ($previous) {
69 30
            $previous->nextSibling = $node;
70
        }
71
72 59
        return $node;
73
    }
74
75
    /**
76
     * @throws WriteOperationForbiddenException
77
     * @throws InvalidSubjectNodeException
78
     * @throws InvalidReferenceNodeException
79
     */
80 29
    protected function insertNode(Node $node, $key, Node $beforeNode = null): Node
81
    {
82 29
        if ($beforeNode === null) {
83 13
            return $this->appendNode($node, $key);
84
        }
85
86 25
        if ($this->activeIteratorCount !== 0) {
87 1
            throw new WriteOperationForbiddenException('Cannot modify a vector with an active iterator');
88
        }
89
90 24
        if ($node->ownerDocument !== $this->ownerDocument) {
91 4
            throw new InvalidSubjectNodeException('Node belongs to a different document');
92
        }
93
94 20
        if ($node->parent !== null) {
95 6
            throw new InvalidSubjectNodeException('Node already present in the document');
96
        }
97
98 14
        if ($beforeNode->parent !== $this) {
99 1
            throw new InvalidReferenceNodeException('Reference node not present in children of this node');
100
        }
101
102 13
        $node->parent = $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this of type void is incompatible with the declared type null|DaveRandom\Jom\Node of property $parent.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
103 13
        $node->key = $key;
104 13
        $this->children[$key] = $node;
105
106 13
        $node->nextSibling = $beforeNode;
107 13
        $beforeNode->previousSibling = $node;
108
109 13
        if ($this->firstChild === $beforeNode) {
110 13
            $this->firstChild = $node;
111
        }
112
113 13
        return $node;
114
    }
115
116
    /**
117
     * @throws WriteOperationForbiddenException
118
     * @throws InvalidSubjectNodeException
119
     * @throws InvalidReferenceNodeException
120
     */
121
    protected function replaceNode(Node $newNode, Node $oldNode): Node
122
    {
123
        if ($this->activeIteratorCount !== 0) {
124
            throw new WriteOperationForbiddenException('Cannot modify a vector with an active iterator');
125
        }
126
127
        if ($newNode->ownerDocument !== $this->ownerDocument) {
128
            throw new InvalidSubjectNodeException('Node belongs to a different document');
129
        }
130
131
        if ($newNode->parent !== null) {
132
            throw new InvalidSubjectNodeException('Node already present in the document');
133
        }
134
135
        if ($oldNode->parent !== $this) {
136
            throw new InvalidReferenceNodeException('Reference node not present in children of this node');
137
        }
138
139
        $newNode->parent = $oldNode->parent;
0 ignored issues
show
Documentation Bug introduced by
It seems like $oldNode->parent of type void is incompatible with the declared type null|DaveRandom\Jom\Node of property $parent.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
140
        $newNode->previousSibling = $oldNode->previousSibling;
141
        $newNode->nextSibling = $oldNode->nextSibling;
142
143
        $newNode->key = $oldNode->key;
144
        $this->children[$oldNode->key] = $newNode;
145
146
        if ($oldNode->previousSibling) {
147
            $oldNode->previousSibling->nextSibling = $newNode;
148
        }
149
150
        if ($oldNode->nextSibling) {
151
            $oldNode->nextSibling->previousSibling = $newNode;
152
        }
153
154
        $oldNode->key = null;
155
        $oldNode->parent = null;
156
        $oldNode->previousSibling = null;
157
        $oldNode->nextSibling = null;
158
159
        return $oldNode;
160
    }
161
162
    /**
163
     * @throws WriteOperationForbiddenException
164
     * @throws InvalidSubjectNodeException
165
     */
166 17
    protected function removeNode(Node $node): Node
167
    {
168 17
        if ($this->activeIteratorCount !== 0) {
169
            throw new WriteOperationForbiddenException('Cannot modify a vector with an active iterator');
170
        }
171
172 17
        if ($node->parent !== $this) {
173
            throw new InvalidSubjectNodeException('Node not present in children of this node');
174
        }
175
176 17
        if ($this->firstChild === $node) {
177 15
            $this->firstChild = $node->nextSibling;
178
        }
179
180 17
        if ($this->lastChild === $node) {
181 15
            $this->lastChild = $node->previousSibling;
182
        }
183
184 17
        if ($node->previousSibling) {
185 8
            $node->previousSibling->nextSibling = $node->nextSibling;
186
        }
187
188 17
        if ($node->nextSibling) {
189 8
            $node->nextSibling->previousSibling = $node->previousSibling;
190
        }
191
192 17
        $node->parent = null;
193 17
        $node->previousSibling = null;
194 17
        $node->nextSibling = null;
195
196 17
        unset($this->children[$node->key]);
197 17
        $node->key = null;
198
199 17
        return $node;
200
    }
201
202
    public function hasChildren(): bool
203
    {
204
        return !empty($this->children);
205
    }
206
207 8
    public function getFirstChild(): ?Node
208
    {
209 8
        return $this->firstChild;
210
    }
211
212 8
    public function getLastChild(): ?Node
213
    {
214 8
        return $this->lastChild;
215
    }
216
217
    final public function clear(): void
218
    {
219
        try {
220
            while ($this->lastChild !== null) {
221
                $this->removeNode($this->lastChild);
222
            }
223
        } catch (\Exception $e) {
224
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
225
        }
226
    }
227
228
    final public function getIterator(): NodeListIterator
229
    {
230 2
        return new NodeListIterator($this->firstChild, function($state) {
0 ignored issues
show
Bug introduced by
It seems like $this->firstChild can also be of type null; however, parameter $firstNode of DaveRandom\Jom\NodeListIterator::__construct() does only seem to accept DaveRandom\Jom\Node, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
        return new NodeListIterator(/** @scrutinizer ignore-type */ $this->firstChild, function($state) {
Loading history...
231 2
            $this->activeIteratorCount += $state === NodeListIterator::INACTIVE
232 2
                ? -1
233 2
                : 1;
234
235 2
            \assert($this->activeIteratorCount >= 0, new \Error('Vector node active iterator count is negative'));
236 2
        });
237
    }
238
239
    final public function count(): int
240
    {
241
        return \count($this->children);
242
    }
243
244
    final public function jsonSerialize(): array
245
    {
246
        return \iterator_to_array($this->getIterator());
247
    }
248
249
    public function offsetExists($key): bool
250
    {
251
        return isset($this->children[$key]);
252
    }
253
254
    /**
255
     * @throws WriteOperationForbiddenException
256
     * @throws InvalidSubjectNodeException
257
     */
258
    public function offsetUnset($key): void
259
    {
260
        if (isset($this->children[$key])) {
261
            $this->removeNode($this->children[$key]);
262
        }
263
    }
264
265
    abstract public function toArray(): array;
266
    abstract public function offsetGet($index): Node;
267
    abstract public function offsetSet($index, $value): void;
268
}
269