Passed
Branch dev/3.0.0 (c487fc)
by Gilles
01:48
created

InnerNode::addChild()   B

Complexity

Conditions 10
Paths 20

Size

Total Lines 72
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 10.017

Importance

Changes 0
Metric Value
cc 10
eloc 37
c 0
b 0
f 0
nc 20
nop 2
dl 0
loc 72
ccs 34
cts 36
cp 0.9444
crap 10.017
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPHtmlParser\Dom\Node;
6
7
use PHPHtmlParser\Exceptions\ChildNotFoundException;
8
use PHPHtmlParser\Exceptions\CircularException;
9
use PHPHtmlParser\Exceptions\LogicalException;
10
use stringEncode\Encode;
11
12
/**
13
 * Inner node of the html tree, might have children.
14
 */
15
abstract class InnerNode extends ArrayNode
16
{
17
    /**
18
     * An array of all the children.
19
     *
20
     * @var array
21
     */
22
    protected $children = [];
23
24
    /**
25
     * Sets the encoding class to this node and propagates it
26
     * to all its children.
27
     */
28 279
    public function propagateEncoding(Encode $encode): void
29
    {
30 279
        $this->encode = $encode;
31 279
        $this->tag->setEncoding($encode);
32
        // check children
33 279
        foreach ($this->children as $child) {
34
            /** @var AbstractNode $node */
35 279
            $node = $child['node'];
36 279
            $node->propagateEncoding($encode);
37
        }
38 279
    }
39
40
    /**
41
     * Checks if this node has children.
42
     */
43 489
    public function hasChildren(): bool
44
    {
45 489
        return !empty($this->children);
46
    }
47
48
    /**
49
     * Returns the child by id.
50
     *
51
     * @throws ChildNotFoundException
52
     */
53 429
    public function getChild(int $id): AbstractNode
54
    {
55 429
        if (!isset($this->children[$id])) {
56 3
            throw new ChildNotFoundException("Child '$id' not found in this node.");
57
        }
58
59 426
        return $this->children[$id]['node'];
60
    }
61
62
    /**
63
     * Returns a new array of child nodes.
64
     */
65 15
    public function getChildren(): array
66
    {
67 15
        $nodes = [];
68 15
        $childrenIds = [];
69
        try {
70 15
            $child = $this->firstChild();
71
            do {
72 12
                $nodes[] = $child;
73 12
                $childrenIds[] = $child->id;
74 12
                $child = $this->nextChild($child->id());
75 12
                if (\in_array($child->id, $childrenIds, true)) {
76
                    throw new CircularException('Circular sibling referance found. Child with id ' . $child->id() . ' found twice.');
77
                }
78 12
            } while (true);
79 15
        } catch (ChildNotFoundException $e) {
80
            // we are done looking for children
81 15
            unset($e);
82
        }
83
84 15
        return $nodes;
85
    }
86
87
    /**
88
     * Counts children.
89
     */
90 6
    public function countChildren(): int
91
    {
92 6
        return \count($this->children);
93
    }
94
95
    /**
96
     * Adds a child node to this node and returns the id of the child for this
97
     * parent.
98
     *
99
     * @throws ChildNotFoundException
100
     * @throws CircularException
101
     * @throws LogicalException
102
     */
103 480
    public function addChild(AbstractNode $child, int $before = -1): bool
104
    {
105 480
        $key = null;
106
107
        // check integrity
108 480
        if ($this->isAncestor($child->id())) {
109 3
            throw new CircularException('Can not add child. It is my ancestor.');
110
        }
111
112
        // check if child is itself
113 480
        if ($child->id() == $this->id) {
114 3
            throw new CircularException('Can not set itself as a child.');
115
        }
116
117 477
        $next = null;
118
119 477
        if ($this->hasChildren()) {
120 462
            if (isset($this->children[$child->id()])) {
121
                // we already have this child
122 450
                return false;
123
            }
124
125 342
            if ($before >= 0) {
126 9
                if (!isset($this->children[$before])) {
127
                    return false;
128
                }
129
130 9
                $key = $this->children[$before]['prev'];
131
132 9
                if ($key) {
133 6
                    $this->children[$key]['next'] = $child->id();
134
                }
135
136 9
                $this->children[$before]['prev'] = $child->id();
137 9
                $next = $before;
138
            } else {
139 342
                $sibling = $this->lastChild();
140 342
                $key = $sibling->id();
141
142 342
                $this->children[$key]['next'] = $child->id();
143
            }
144
        }
145
146 477
        $keys = \array_keys($this->children);
147
148
        $insert = [
149 477
            'node' => $child,
150 477
            'next' => $next,
151 477
            'prev' => $key,
152
        ];
153
154 477
        $index = $key ? (int) (\array_search($key, $keys, true) + 1) : 0;
155 477
        \array_splice($keys, $index, 0, (string) $child->id());
156
157 477
        $children = \array_values($this->children);
158 477
        \array_splice($children, $index, 0, [$insert]);
159
160
        // add the child
161 477
        $combination = \array_combine($keys, $children);
162 477
        if ($combination === false) {
163
            // The number of elements for each array isn't equal or if the arrays are empty.
164
            throw new LogicalException('array combine failed during add child method call.');
165
        }
166 477
        $this->children = $combination;
167
168
        // tell child I am the new parent
169 477
        $child->setParent($this);
170
171
        //clear any cache
172 477
        $this->clear();
173
174 477
        return true;
175
    }
176
177
    /**
178
     * Insert element before child with provided id.
179
     *
180
     * @throws ChildNotFoundException
181
     * @throws CircularException
182
     */
183 6
    public function insertBefore(AbstractNode $child, int $id): bool
184
    {
185 6
        return $this->addChild($child, $id);
186
    }
187
188
    /**
189
     * Insert element before after with provided id.
190
     *
191
     * @throws ChildNotFoundException
192
     * @throws CircularException
193
     */
194 6
    public function insertAfter(AbstractNode $child, int $id): bool
195
    {
196 6
        if (!isset($this->children[$id])) {
197
            return false;
198
        }
199
200 6
        if (isset($this->children[$id]['next']) && \is_int($this->children[$id]['next'])) {
201 3
            return $this->addChild($child, (int) $this->children[$id]['next']);
202
        }
203
204
        // clear cache
205 3
        $this->clear();
206
207 3
        return $this->addChild($child);
208
    }
209
210
    /**
211
     * Removes the child by id.
212
     *
213
     *
214
     */
215 24
    public function removeChild(int $id): InnerNode
216
    {
217 24
        if (!isset($this->children[$id])) {
218 3
            return $this;
219
        }
220
221
        // handle moving next and previous assignments.
222 21
        $next = $this->children[$id]['next'];
223 21
        $prev = $this->children[$id]['prev'];
224 21
        if (!\is_null($next)) {
225 6
            $this->children[$next]['prev'] = $prev;
226
        }
227 21
        if (!\is_null($prev)) {
228 9
            $this->children[$prev]['next'] = $next;
229
        }
230
231
        // remove the child
232 21
        unset($this->children[$id]);
233
234
        //clear any cache
235 21
        $this->clear();
236
237 21
        return $this;
238
    }
239
240
    /**
241
     * Check if has next Child.
242
     *
243
     * @throws ChildNotFoundException
244
     *
245
     * @return mixed
246
     */
247 6
    public function hasNextChild(int $id)
248
    {
249 6
        $child = $this->getChild($id);
250
251 3
        return $this->children[$child->id()]['next'];
252
    }
253
254
    /**
255
     * Attempts to get the next child.
256
     *
257
     * @throws ChildNotFoundException
258
     *
259
     * @uses $this->getChild()
260
     */
261 387
    public function nextChild(int $id): AbstractNode
262
    {
263 387
        $child = $this->getChild($id);
264 387
        $next = $this->children[$child->id()]['next'];
265 387
        if (\is_null($next) || !\is_int($next)) {
266 366
            throw new ChildNotFoundException("Child '$id' next sibling not found in this node.");
267
        }
268
269 306
        return $this->getChild($next);
270
    }
271
272
    /**
273
     * Attempts to get the previous child.
274
     *
275
     * @throws ChildNotFoundException
276
     *
277
     * @uses $this->getChild()
278
     */
279 12
    public function previousChild(int $id): AbstractNode
280
    {
281 12
        $child = $this->getchild($id);
282 12
        $next = $this->children[$child->id()]['prev'];
283 12
        if (\is_null($next) || !\is_int($next)) {
284 3
            throw new ChildNotFoundException("Child '$id' previous not found in this node.");
285
        }
286
287 9
        return $this->getChild($next);
288
    }
289
290
    /**
291
     * Checks if the given node id is a child of the
292
     * current node.
293
     */
294 462
    public function isChild(int $id): bool
295
    {
296 462
        foreach (\array_keys($this->children) as $childId) {
297 39
            if ($id == $childId) {
298 18
                return true;
299
            }
300
        }
301
302 462
        return false;
303
    }
304
305
    /**
306
     * Removes the child with id $childId and replace it with the new child
307
     * $newChild.
308
     *
309
     * @throws LogicalException
310
     */
311 6
    public function replaceChild(int $childId, AbstractNode $newChild): void
312
    {
313 6
        $oldChild = $this->children[$childId];
314
315 6
        $newChild->prev = (int) $oldChild['prev'];
316 6
        $newChild->next = (int) $oldChild['next'];
317
318 6
        $keys = \array_keys($this->children);
319 6
        $index = \array_search($childId, $keys, true);
320 6
        $keys[$index] = $newChild->id();
321 6
        $combination = \array_combine($keys, $this->children);
322 6
        if ($combination === false) {
323
            // The number of elements for each array isn't equal or if the arrays are empty.
324
            throw new LogicalException('array combine failed during replace child method call.');
325
        }
326 6
        $this->children = $combination;
327 6
        $this->children[$newChild->id()] = [
328 6
            'prev' => $oldChild['prev'],
329 6
            'node' => $newChild,
330 6
            'next' => $oldChild['next'],
331
        ];
332
333
        // change previous child id to new child
334 6
        if ($oldChild['prev'] && isset($this->children[$newChild->prev])) {
335
            $this->children[$oldChild['prev']]['next'] = $newChild->id();
336
        }
337
338
        // change next child id to new child
339 6
        if ($oldChild['next'] && isset($this->children[$newChild->next])) {
340 3
            $this->children[$oldChild['next']]['prev'] = $newChild->id();
341
        }
342
343
        // remove old child
344 6
        unset($this->children[$childId]);
345
346
        // clean out cache
347 6
        $this->clear();
348 6
    }
349
350
    /**
351
     * Shortcut to return the first child.
352
     *
353
     * @throws ChildNotFoundException
354
     *
355
     * @uses $this->getChild()
356
     */
357 378
    public function firstChild(): AbstractNode
358
    {
359 378
        if (\count($this->children) == 0) {
360
            // no children
361 3
            throw new ChildNotFoundException('No children found in node.');
362
        }
363
364 378
        \reset($this->children);
365 378
        $key = (int) \key($this->children);
366
367 378
        return $this->getChild($key);
368
    }
369
370
    /**
371
     * Attempts to get the last child.
372
     *
373
     * @throws ChildNotFoundException
374
     *
375
     * @uses $this->getChild()
376
     */
377 342
    public function lastChild(): AbstractNode
378
    {
379 342
        if (\count($this->children) == 0) {
380
            // no children
381
            throw new ChildNotFoundException('No children found in node.');
382
        }
383
384 342
        \end($this->children);
385 342
        $key = \key($this->children);
386
387 342
        if (!\is_int($key)) {
388
            throw new LogicalException('Children array contain child with a key that is not an int.');
389
        }
390
391 342
        return $this->getChild($key);
392
    }
393
394
    /**
395
     * Checks if the given node id is a descendant of the
396
     * current node.
397
     */
398 462
    public function isDescendant(int $id): bool
399
    {
400 462
        if ($this->isChild($id)) {
401 6
            return true;
402
        }
403
404 462
        foreach ($this->children as $child) {
405
            /** @var InnerNode $node */
406 21
            $node = $child['node'];
407 21
            if ($node instanceof InnerNode
408 21
              && $node->hasChildren()
409 21
              && $node->isDescendant($id)
410
            ) {
411 3
                return true;
412
            }
413
        }
414
415 462
        return false;
416
    }
417
418
    /**
419
     * Sets the parent node.
420
     *
421
     * @throws ChildNotFoundException
422
     * @throws CircularException
423
     */
424 462
    public function setParent(InnerNode $parent): AbstractNode
425
    {
426
        // check integrity
427 462
        if ($this->isDescendant($parent->id())) {
428 3
            throw new CircularException('Can not add descendant "' . $parent->id() . '" as my parent.');
429
        }
430
431
        // clear cache
432 462
        $this->clear();
433
434 462
        return parent::setParent($parent);
435
    }
436
}
437