Completed
Push — master ( 8b759a...268bdc )
by Gilles
02:53
created

InnerNode   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Test Coverage

Coverage 97.84%

Importance

Changes 0
Metric Value
eloc 125
dl 0
loc 415
ccs 136
cts 139
cp 0.9784
rs 8.48
c 0
b 0
f 0
wmc 49

18 Methods

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

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 189
    public function propagateEncoding(Encode $encode): void
31
    {
32 189
        $this->encode = $encode;
33 189
        $this->tag->setEncoding($encode);
34
        // check children
35 189
        foreach ($this->children as $id => $child) {
36
            /** @var AbstractNode $node */
37 189
            $node = $child['node'];
38 189
            $node->propagateEncoding($encode);
39
        }
40 189
    }
41
42
    /**
43
     * Checks if this node has children.
44
     *
45
     * @return bool
46
     */
47 393
    public function hasChildren(): bool
48
    {
49 393
        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 336
    public function getChild(int $id): AbstractNode
60
    {
61 336
        if ( ! isset($this->children[$id])) {
62 3
            throw new ChildNotFoundException("Child '$id' not found in this node.");
63
        }
64
65 333
        return $this->children[$id]['node'];
66
    }
67
68
    /**
69
     * Returns a new array of child nodes
70
     *
71
     * @return array
72
     */
73 12
    public function getChildren(): array
74
    {
75 12
        $nodes = [];
76
        try {
77 12
            $child = $this->firstChild();
78
            do {
79 12
                $nodes[] = $child;
80 12
                $child   = $this->nextChild($child->id());
81 12
            } while ( ! is_null($child));
82 12
        } catch (ChildNotFoundException $e) {
83
            // we are done looking for children
84
        }
85
86 12
        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 387
    public function addChild(AbstractNode $child, int $before = -1): bool
109
    {
110 387
        $key = null;
111
112
        // check integrity
113 387
        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 387
        if ($child->id() == $this->id) {
119 3
            throw new CircularException('Can not set itself as a child.');
120
        }
121
122 384
		$next = null;
123
124 384
        if ($this->hasChildren()) {
125 369
			if (isset($this->children[$child->id()])) {
126
				// we already have this child
127 357
				return false;
128
			}
129
130 300
			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 300
				$sibling = $this->lastChild();
145 300
				$key = $sibling->id();
146
147 300
				$this->children[$key]['next'] = $child->id();
148
			}
149
        }
150
151 384
		$keys = array_keys($this->children);
152
153
		$insert = [
154 384
			'node' => $child,
155 384
			'next' => $next,
156 384
			'prev' => $key,
157
		];
158
159 384
		$index = $key ? (array_search($key, $keys, true) + 1) : 0;
160 384
		array_splice($keys, $index, 0, $child->id());
161
162 384
		$children = array_values($this->children);
163 384
		array_splice($children, $index, 0, [$insert]);
164
165
		// add the child
166 384
		$this->children = array_combine($keys, $children);
167
168
        // tell child I am the new parent
169 384
        $child->setParent($this);
170
171
        //clear any cache
172 384
        $this->clear();
173
174 384
        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 294
    public function nextChild(int $id): AbstractNode
265
    {
266 294
        $child = $this->getChild($id);
267 294
        $next  = $this->children[$child->id()]['next'];
268 294
        if (is_null($next)) {
269 273
            throw new ChildNotFoundException("Child '$id' next not found in this node.");
270
        }
271
272 264
        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 375
    public function isChild(int $id): bool
302
    {
303 375
        foreach ($this->children as $childId => $child) {
304 36
            if ($id == $childId) {
305 30
                return true;
306
            }
307
        }
308
309 375
        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])) {
0 ignored issues
show
Bug Best Practice introduced by
The property prev does not exist on PHPHtmlParser\Dom\AbstractNode. Since you implemented __get, consider adding a @property annotation.
Loading history...
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])) {
0 ignored issues
show
Bug Best Practice introduced by
The property next does not exist on PHPHtmlParser\Dom\AbstractNode. Since you implemented __get, consider adding a @property annotation.
Loading history...
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
     */
361 285
    public function firstChild(): AbstractNode
362
    {
363 285
        reset($this->children);
364 285
        $key = key($this->children);
365
366 285
        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

366
        return $this->getChild(/** @scrutinizer ignore-type */ $key);
Loading history...
367
    }
368
369
    /**
370
     * Attempts to get the last child.
371
     *
372
     * @return AbstractNode
373
     */
374 300
    public function lastChild(): AbstractNode
375
    {
376 300
        end($this->children);
377 300
        $key = key($this->children);
378
379 300
        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

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