Passed
Pull Request — master (#225)
by
unknown
12:21
created

AbstractNode::hasAttribute()   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 1
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 516
    public function __construct()
80
    {
81 516
        $this->id = self::$count;
82 516
        self::$count++;
83 516
    }
84
85
    /**
86
     * Magic get method for attributes and certain methods.
87
     *
88
     * @param string $key
89
     * @return mixed
90
     */
91 177
    public function __get(string $key)
92
    {
93
        // check attribute first
94 177
        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 159
        switch (strtolower($key)) {
98 159
            case 'outerhtml':
99 36
                return $this->outerHtml();
100 123
            case 'innerhtml':
101 66
                return $this->innerHtml();
102 60
            case 'innertext':
103 3
                return $this->innerText();
0 ignored issues
show
Bug introduced by
The method innerText() does not exist on PHPHtmlParser\Dom\AbstractNode. It seems like you code against a sub-type of PHPHtmlParser\Dom\AbstractNode such as PHPHtmlParser\Dom\HtmlNode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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