Passed
Push — master ( 12b94f...668c77 )
by Gilles
03:31
created

AbstractNode::id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Exceptions\CircularException;
5
use PHPHtmlParser\Exceptions\ParentNotFoundException;
6
use PHPHtmlParser\Exceptions\ChildNotFoundException;
7
use PHPHtmlParser\Selector\Selector;
8
use PHPHtmlParser\Selector\Parser as SelectorParser;
9
use stringEncode\Encode;
10
use PHPHtmlParser\Finder;
11
12
/**
13
 * Dom node object.
14
 * @property string    $outerhtml
15
 * @property string    $innerhtml
16
 * @property string    $text
17
 * @property int       $prev
18
 * @property int       $next
19
 * @property Tag       $tag
20
 * @property InnerNode $parent
21
 */
22
abstract class AbstractNode
23
{
24
    /**
25
     * @var int
26
     */
27
    private static $count = 0;
28
29
    /**
30
     * Contains the tag name/type
31
     *
32
     * @var ?Tag
33
     */
34
    protected $tag = null;
35
36
    /**
37
     * Contains a list of attributes on this tag.
38
     *
39
     * @var array
40
     */
41
    protected $attr = [];
42
43
    /**
44
     * Contains the parent Node.
45
     *
46
     * @var ?InnerNode
47
     */
48
    protected $parent = null;
49
50
    /**
51
     * The unique id of the class. Given by PHP.
52
     *
53
     * @var int
54
     */
55
    protected $id;
56
57
    /**
58
     * The encoding class used to encode strings.
59
     *
60
     * @var mixed
61
     */
62
    protected $encode;
63
64
    /**
65
     * An array of all the children.
66
     *
67
     * @var array
68
     */
69
    protected $children = [];
70
71
    /**
72
     * @var bool
73
     */
74
    protected $htmlSpecialCharsDecode = false;
75
76
    /**
77
     * Creates a unique id for this node.
78
     */
79 510
    public function __construct()
80
    {
81 510
        $this->id = self::$count;
82 510
        self::$count++;
83 510
    }
84
85
    /**
86
     * Magic get method for attributes and certain methods.
87
     *
88
     * @param string $key
89
     * @return mixed
90
     */
91 174
    public function __get(string $key)
92
    {
93
        // check attribute first
94 174
        if ( ! is_null($this->getAttribute($key))) {
0 ignored issues
show
introduced by
The condition is_null($this->getAttribute($key)) is always true.
Loading history...
Bug introduced by
Are you sure the usage of $this->getAttribute($key) targeting PHPHtmlParser\Dom\AbstractNode::getAttribute() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
95 21
            return $this->getAttribute($key);
96
        }
97 156
        switch (strtolower($key)) {
98 156
            case 'outerhtml':
99 36
                return $this->outerHtml();
100 120
            case 'innerhtml':
101 66
                return $this->innerHtml();
102 57
            case 'text':
103 51
                return $this->text();
104 6
            case 'tag':
105 3
                return $this->getTag();
106 3
            case 'parent':
107
                return $this->getParent();
108
        }
109
110 3
        return null;
111
    }
112
113
    /**
114
     * Attempts to clear out any object references.
115
     */
116 126
    public function __destruct()
117
    {
118 126
        $this->tag      = null;
119 126
        $this->parent   = null;
120 126
        $this->attr     = [];
121 126
        $this->children = [];
122 126
    }
123
124
    /**
125
     * Simply calls the outer text method.
126
     *
127
     * @return string
128
     */
129 12
    public function __toString()
130
    {
131 12
        return $this->outerHtml();
132
    }
133
134
    /**
135
     * @param bool $htmlSpecialCharsDecode
136
     * @return void
137
     */
138 246
    public function setHtmlSpecialCharsDecode($htmlSpecialCharsDecode = false): void
139
    {
140 246
        $this->htmlSpecialCharsDecode = $htmlSpecialCharsDecode;
141 246
    }
142
143
144
    /**
145
     * Reset node counter
146
     *
147
     * @return void
148
     */
149 183
    public static function resetCount()
150
    {
151 183
        self::$count = 0;
152 183
    }
153
154
    /**
155
     * Returns the id of this object.
156
     *
157
     * @return int
158
     */
159 444
    public function id(): int
160
    {
161 444
        return $this->id;
162
    }
163
164
    /**
165
     * Returns the parent of node.
166
     *
167
     * @return AbstractNode
168
     */
169 240
    public function getParent()
170
    {
171 240
        return $this->parent;
172
    }
173
174
    /**
175
     * Sets the parent node.
176
     * @param InnerNode $parent
177
     * @return AbstractNode
178
     * @throws ChildNotFoundException
179
     * @throws CircularException
180
     */
181 438
    public function setParent(InnerNode $parent): AbstractNode
182
    {
183
        // remove from old parent
184 438
        if ( ! is_null($this->parent)) {
185 30
            if ($this->parent->id() == $parent->id()) {
186
                // already the parent
187 27
                return $this;
188
            }
189
190 6
            $this->parent->removeChild($this->id);
191
        }
192
193 438
        $this->parent = $parent;
194
195
        // assign child to parent
196 438
        $this->parent->addChild($this);
197
198 438
        return $this;
199
    }
200
201
    /**
202
     * Removes this node and all its children from the
203
     * DOM tree.
204
     *
205
     * @return void
206
     */
207 3
    public function delete()
208
    {
209 3
        if ( ! is_null($this->parent)) {
210 3
            $this->parent->removeChild($this->id);
211
        }
212 3
        $this->parent->clear();
213 3
        $this->clear();
214 3
    }
215
216
    /**
217
     * Sets the encoding class to this node.
218
     *
219
     * @param Encode $encode
220
     * @return void
221
     */
222 222
    public function propagateEncoding(Encode $encode)
223
    {
224 222
        $this->encode = $encode;
225 222
        $this->tag->setEncoding($encode);
226 222
    }
227
228
    /**
229
     * Checks if the given node id is an ancestor of
230
     * the current node.
231
     *
232
     * @param int $id
233
     * @return bool
234
     */
235 441
    public function isAncestor(int $id): Bool
236
    {
237 441
        if ( ! is_null($this->getAncestor($id))) {
238 6
            return true;
239
        }
240
241 441
        return false;
242
    }
243
244
    /**
245
     * Attempts to get an ancestor node by the given id.
246
     *
247
     * @param int $id
248
     * @return null|AbstractNode
249
     */
250 444
    public function getAncestor(int $id)
251
    {
252 444
        if ( ! is_null($this->parent)) {
253 309
            if ($this->parent->id() == $id) {
254 12
                return $this->parent;
255
            }
256
257 300
            return $this->parent->getAncestor($id);
258
        }
259
260 444
        return null;
261
    }
262
263
    /**
264
     * Checks if the current node has a next sibling.
265
     *
266
     * @return bool
267
     */
268 9
    public function hasNextSibling(): bool
269
    {
270
        try
271
        {
272 9
            $this->nextSibling();
273
274
            // sibling found, return true;
275 9
            return true;
276
        }
277 9
        catch (ParentNotFoundException $e)
278
        {
279
            // no parent, no next sibling
280
            unset($e);
281
            return false;
282
        }
283 9
        catch (ChildNotFoundException $e)
284
        {
285
            // no sibling found
286 9
            unset($e);
287 9
            return false;
288
        }
289
    }
290
291
    /**
292
     * Attempts to get the next sibling.
293
     * @return AbstractNode
294
     * @throws ChildNotFoundException
295
     * @throws ParentNotFoundException
296
     */
297 39
    public function nextSibling(): AbstractNode
298
    {
299 39
        if (is_null($this->parent)) {
300 3
            throw new ParentNotFoundException('Parent is not set for this node.');
301
        }
302
303 36
        return $this->parent->nextChild($this->id);
304
    }
305
306
    /**
307
     * Attempts to get the previous sibling.
308
     * @return AbstractNode
309
     * @throws ChildNotFoundException
310
     * @throws ParentNotFoundException
311
     */
312 9
    public function previousSibling(): AbstractNode
313
    {
314 9
        if (is_null($this->parent)) {
315 3
            throw new ParentNotFoundException('Parent is not set for this node.');
316
        }
317
318 6
        return $this->parent->previousChild($this->id);
319
    }
320
321
    /**
322
     * Gets the tag object of this node.
323
     *
324
     * @return Tag
325
     */
326 315
    public function getTag(): Tag
327
    {
328 315
        return $this->tag;
329
    }
330
331
    /**
332
     * A wrapper method that simply calls the getAttribute method
333
     * on the tag of this node.
334
     *
335
     * @return array
336
     */
337 6
    public function getAttributes(): array
338
    {
339 6
        $attributes = $this->tag->getAttributes();
340 6
        foreach ($attributes as $name => $info) {
341 3
            $attributes[$name] = $info['value'];
342
        }
343
344 6
        return $attributes;
345
    }
346
347
    /**
348
     * A wrapper method that simply calls the getAttribute method
349
     * on the tag of this node.
350
     *
351
     * @param string $key
352
     * @return string|null
353
     */
354 237
    public function getAttribute(string $key): ?string
355
    {
356 237
        $attribute = $this->tag->getAttribute($key);
357 237
        $attributeValue = $attribute['value'];
358
359 237
        return $attributeValue;
360
    }
361
362
    /**
363
     * A wrapper method that simply calls the hasAttribute method
364
     * on the tag of this node.
365
     *
366
     * @param string $key
367
     * @return bool
368
     */
369 99
    public function hasAttribute(string $key): bool
370
    {
371 99
        return $this->tag->hasAttribute($key);
372
    }
373
374
    /**
375
     * A wrapper method that simply calls the setAttribute method
376
     * on the tag of this node.
377
     *
378
     * @param string $key
379
     * @param string|array $value
380
     * @return AbstractNode
381
     * @chainable
382
     */
383 24
    public function setAttribute(string $key, $value): AbstractNode
384
    {
385 24
        $this->tag->setAttribute($key, $value);
386
387
        //clear any cache
388 24
        $this->clear();
389
390 24
        return $this;
391
    }
392
393
    /**
394
     * A wrapper method that simply calls the removeAttribute method
395
     * on the tag of this node.
396
     *
397
     * @param string $key
398
     * @return void
399
     */
400 3
    public function removeAttribute(string $key): void
401
    {
402 3
        $this->tag->removeAttribute($key);
403
404
        //clear any cache
405 3
        $this->clear();
406 3
    }
407
408
    /**
409
     * A wrapper method that simply calls the removeAllAttributes
410
     * method on the tag of this node.
411
     *
412
     * @return void
413
     */
414 3
    public function removeAllAttributes(): void
415
    {
416 3
        $this->tag->removeAllAttributes();
417
418
        //clear any cache
419 3
        $this->clear();
420 3
    }
421
    /**
422
     * Function to locate a specific ancestor tag in the path to the root.
423
     *
424
     * @param  string $tag
425
     * @return AbstractNode
426
     * @throws ParentNotFoundException
427
     */
428 6
    public function ancestorByTag(string $tag): AbstractNode
429
    {
430
        // Start by including ourselves in the comparison.
431 6
        $node = $this;
432
433 6
        while ( ! is_null($node)) {
434 6
            if ($node->tag->name() == $tag) {
435 3
                return $node;
436
            }
437
438 3
            $node = $node->getParent();
439
        }
440
441 3
        throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
442
    }
443
444
    /**
445
     * Find elements by css selector
446
     * @param string   $selector
447
     * @param int|null $nth
448
     * @param bool     $depthFirst
449
     * @return mixed|Collection|null
450
     * @throws ChildNotFoundException
451
     */
452 240
    public function find(string $selector, int $nth = null, bool $depthFirst = false)
453
    {
454 240
        $selector = new Selector($selector, new SelectorParser());
455 240
        $selector->setDepthFirstFind($depthFirst);
456 240
        $nodes    = $selector->find($this);
457
458 240
        if ( ! is_null($nth)) {
459
            // return nth-element or array
460 240
            if (isset($nodes[$nth])) {
461 123
                return $nodes[$nth];
462
            }
463
464 210
            return null;
465
        }
466
467 84
        return $nodes;
468
    }
469
470
    /**
471
     * Find node by id
472
     * @param int $id
473
     * @return bool|AbstractNode
474
     * @throws ChildNotFoundException
475
     * @throws ParentNotFoundException
476
     */
477 9
    public function findById(int $id)
478
    {
479 9
        $finder= new Finder($id);
480
481 9
        return $finder->find($this);
482
    }
483
484
485
    /**
486
     * Gets the inner html of this node.
487
     *
488
     * @return string
489
     */
490
    abstract public function innerHtml(): string;
491
492
    /**
493
     * Gets the html of this node, including it's own
494
     * tag.
495
     *
496
     * @return string
497
     */
498
    abstract public function outerHtml(): string;
499
500
    /**
501
     * Gets the text of this node (if there is any text).
502
     *
503
     * @return string
504
     */
505
    abstract public function text(): string;
506
507
    /**
508
     * Call this when something in the node tree has changed. Like a child has been added
509
     * or a parent has been changed.
510
     *
511
     * @return void
512
     */
513
    abstract protected function clear(): void;
514
515
    /**
516
     * Check is node type textNode
517
     *
518
     * @return boolean
519
     */
520 9
    public function isTextNode(): bool 
521
    {
522
523 9
        return false;
524
    }
525
}
526