Passed
Push — master ( 268bdc...77e4a4 )
by Gilles
02:56
created

InnerNode   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Test Coverage

Coverage 97.2%

Importance

Changes 0
Metric Value
eloc 129
dl 0
loc 428
ccs 139
cts 143
cp 0.972
rs 7.92
c 0
b 0
f 0
wmc 51

18 Methods

Rating   Name   Duplication   Size   Complexity  
A countChildren() 0 3 1
A hasNextChild() 0 4 1
A insertAfter() 0 14 3
A replaceChild() 0 32 5
A insertBefore() 0 3 1
A hasChildren() 0 3 1
A removeChild() 0 23 4
A nextChild() 0 9 2
A getChildren() 0 14 3
A getChild() 0 7 2
A previousChild() 0 9 2
A propagateEncoding() 0 9 2
B addChild() 0 67 9
A isChild() 0 9 3
A isDescendant() 0 18 6
A lastChild() 0 11 2
A setParent() 0 11 2
A firstChild() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like InnerNode 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 InnerNode, and based on these observations, apply Extract Interface, too.

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

392
        return $this->getChild(/** @scrutinizer ignore-type */ $key);
Loading history...
393
    }
394
395
    /**
396
     * Checks if the given node id is a descendant of the
397
     * current node.
398
     *
399
     * @param int $id
400
     * @return bool
401
     */
402 381
    public function isDescendant(int $id): bool
403
    {
404 381
        if ($this->isChild($id)) {
405 6
            return true;
406
        }
407
408 381
        foreach ($this->children as $childId => $child) {
409
            /** @var InnerNode $node */
410 18
            $node = $child['node'];
411 18
            if ($node instanceof InnerNode &&
412 18
                $node->hasChildren() &&
413 18
                $node->isDescendant($id)
414
            ) {
415 13
                return true;
416
            }
417
        }
418
419 381
        return false;
420
    }
421
422
    /**
423
     * Sets the parent node.
424
     *
425
     * @param InnerNode $parent
426
     * @return AbstractNode
427
     * @throws CircularException
428
     * @chainable
429
     */
430 381
    public function setParent(InnerNode $parent): AbstractNode
431
    {
432
        // check integrity
433 381
        if ($this->isDescendant($parent->id())) {
434 3
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
435
        }
436
437
		// clear cache
438 381
		$this->clear();
439
440 381
        return parent::setParent($parent);
441
    }
442
}
443