Passed
Push — master ( d10009...12b94f )
by Gilles
02:00
created

InnerNode::addChild()   B

Complexity

Conditions 10
Paths 20

Size

Total Lines 70
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 10.0023

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 36
nc 20
nop 2
dl 0
loc 70
ccs 34
cts 35
cp 0.9714
crap 10.0023
rs 7.6666
c 1
b 0
f 0

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

408
        return $this->getChild(/** @scrutinizer ignore-type */ $key);
Loading history...
409
    }
410
411
    /**
412
     * Checks if the given node id is a descendant of the
413
     * current node.
414
     *
415
     * @param int $id
416
     *
417
     * @return bool
418
     */
419 414
    public function isDescendant(int $id): bool
420
    {
421 414
        if ($this->isChild($id)) {
422 6
            return true;
423
        }
424
425 414
        foreach ($this->children as $childId => $child) {
426
            /** @var InnerNode $node */
427 18
            $node = $child['node'];
428 18
            if ($node instanceof InnerNode
429 18
              && $node->hasChildren()
430 18
              && $node->isDescendant($id)
431
            ) {
432 8
                return true;
433
            }
434
        }
435
436 414
        return false;
437
    }
438
439
    /**
440
     * Sets the parent node.
441
     * @param InnerNode $parent
442
     * @return AbstractNode
443
     * @throws ChildNotFoundException
444
     * @throws CircularException
445
     */
446 414
    public function setParent(InnerNode $parent): AbstractNode
447
    {
448
        // check integrity
449 414
        if ($this->isDescendant($parent->id())) {
450 3
            throw new CircularException('Can not add descendant "'
451 3
              . $parent->id() . '" as my parent.');
452
        }
453
454
        // clear cache
455 414
        $this->clear();
456
457 414
        return parent::setParent($parent);
458
    }
459
}
460