Test Failed
Pull Request — master (#160)
by
unknown
11:10
created

AbstractNode::resetCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Exceptions\CircularException;
5
use PHPHtmlParser\Exceptions\ParentNotFoundException;
6
use PHPHtmlParser\Selector;
7
use stringEncode\Encode;
8
use PHPHtmlParser\Finder;
9
10
/**
11
 * Dom node object.
12
 *
13
 * @property string outerhtml
14
 * @property string innerhtml
15
 * @property string text
16
 * @property \PHPHtmlParser\Dom\Tag tag
17
 * @property InnerNode parent
18
 */
19
abstract class AbstractNode
20
{
21
    private static $count = 0;
22
    /**
23
     * Contains the tag name/type
24
     *
25
     * @var \PHPHtmlParser\Dom\Tag
26
     */
27
    protected $tag;
28
29
    /**
30
     * Contains a list of attributes on this tag.
31
     *
32
     * @var array
33
     */
34
    protected $attr = [];
35
36
    /**
37
     * Contains the parent Node.
38
     *
39
     * @var InnerNode
40
     */
41
    protected $parent = null;
42
43
    /**
44
     * The unique id of the class. Given by PHP.
45
     *
46
     * @var string
47
     */
48
    protected $id;
49
50
    /**
51
     * The encoding class used to encode strings.
52
     *
53
     * @var mixed
54
     */
55
    protected $encode;
56
57
    /**
58
     * Creates a unique id for this node.
59
     */
60
    public function __construct()
61
    {
62
        $this->id = self::$count;
0 ignored issues
show
Documentation Bug introduced by
The property $id was declared of type string, but self::$count is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
63
        self::$count++;
64
    }
65
66
    /**
67
     * Magic get method for attributes and certain methods.
68
     *
69
     * @param string $key
70
     * @return mixed
71
     */
72
    public function __get($key)
73
    {
74
        // check attribute first
75
        if ( ! is_null($this->getAttribute($key))) {
76
            return $this->getAttribute($key);
77
        }
78
        switch (strtolower($key)) {
79
            case 'outerhtml':
80
                return $this->outerHtml();
81
            case 'innerhtml':
82
                return $this->innerHtml();
83
            case 'text':
84
                return $this->text();
85
            case 'tag':
86
                return $this->getTag();
87
            case 'parent':
88
                return $this->getParent();
89
        }
90
91
        return null;
92
    }
93
94
    /**
95
     * Attempts to clear out any object references.
96
     */
97
    public function __destruct()
98
    {
99
        $this->tag      = null;
100
        $this->attr     = [];
101
        $this->parent   = null;
102
        $this->children = [];
0 ignored issues
show
Bug introduced by
The property children does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
103
    }
104
105
    /**
106
     * Simply calls the outer text method.
107
     *
108
     * @return string
109
     */
110
    public function __toString()
111
    {
112
        return $this->outerHtml();
113
    }
114
115
    /**
116
     * Reset node counter
117
     */
118
    public static function resetCount()
119
    {
120
        self::$count = 0;
121
    }
122
123
    /**
124
     * Returns the id of this object.
125
     */
126
    public function id()
127
    {
128
        return $this->id;
129
    }
130
131
    /**
132
     * Returns the parent of node.
133
     *
134
     * @return AbstractNode
135
     */
136
    public function getParent()
137
    {
138
        return $this->parent;
139
    }
140
141
    /**
142
     * Sets the parent node.
143
     *
144
     * @param InnerNode $parent
145
     * @return $this
146
     * @throws CircularException
147
     */
148
    public function setParent(InnerNode $parent)
149
    {
150
        // remove from old parent
151
        if ( ! is_null($this->parent)) {
152
            if ($this->parent->id() == $parent->id()) {
153
                // already the parent
154
                return $this;
155
            }
156
157
            $this->parent->removeChild($this->id);
158
        }
159
160
        $this->parent = $parent;
161
162
        // assign child to parent
163
        $this->parent->addChild($this);
164
165
        return $this;
166
    }
167
168
    /**
169
     * Removes this node and all its children from the
170
     * DOM tree.
171
     *
172
     * @return void
173
     */
174
    public function delete()
175
    {
176
        if ( ! is_null($this->parent)) {
177
            $this->parent->removeChild($this->id);
178
        }
179
        $this->parent->clear();
180
        $this->clear();
181
    }
182
183
    /**
184
     * Sets the encoding class to this node.
185
     *
186
     * @param Encode $encode
187
     * @return void
188
     */
189
    public function propagateEncoding(Encode $encode)
190
    {
191
        $this->encode = $encode;
192
        $this->tag->setEncoding($encode);
193
    }
194
195
    /**
196
     * Checks if the given node id is an ancestor of
197
     * the current node.
198
     *
199
     * @param int $id
200
     * @return bool
201
     */
202
    public function isAncestor($id)
203
    {
204
        if ( ! is_null($this->getAncestor($id))) {
205
            return true;
206
        }
207
208
        return false;
209
    }
210
211
    /**
212
     * Attempts to get an ancestor node by the given id.
213
     *
214
     * @param int $id
215
     * @return null|AbstractNode
216
     */
217
    public function getAncestor($id)
218
    {
219
        if ( ! is_null($this->parent)) {
220
            if ($this->parent->id() == $id) {
221
                return $this->parent;
222
            }
223
224
            return $this->parent->getAncestor($id);
225
        }
226
227
        return null;
228
    }
229
230
    public function hasNextSibling()
231
    {
232
        if (is_null($this->parent) || (!$this->parent->hasChildren())) {
233
            return false;
234
        }
235
236
        return $this->parent->hasNextChild($this->id());
237
    }
238
239
    /**
240
     * Attempts to get the next sibling.
241
     *
242
     * @return AbstractNode
243
     * @throws ParentNotFoundException
244
     */
245
    public function nextSibling()
246
    {
247
        if (is_null($this->parent)) {
248
            throw new ParentNotFoundException('Parent is not set for this node.');
249
        }
250
251
        return $this->parent->nextChild($this->id);
252
    }
253
254
    /**
255
     * Attempts to get the previous sibling
256
     *
257
     * @return AbstractNode
258
     * @throws ParentNotFoundException
259
     */
260
    public function previousSibling()
261
    {
262
        if (is_null($this->parent)) {
263
            throw new ParentNotFoundException('Parent is not set for this node.');
264
        }
265
266
        return $this->parent->previousChild($this->id);
267
    }
268
269
    /**
270
     * Gets the tag object of this node.
271
     *
272
     * @return Tag
273
     */
274
    public function getTag()
275
    {
276
        return $this->tag;
277
    }
278
279
    /**
280
     * A wrapper method that simply calls the getAttribute method
281
     * on the tag of this node.
282
     *
283
     * @return array
284
     */
285
    public function getAttributes()
286
    {
287
        $attributes = $this->tag->getAttributes();
288
        foreach ($attributes as $name => $info) {
289
            $attributes[$name] = $info['value'];
290
        }
291
292
        return $attributes;
293
    }
294
295
    /**
296
     * A wrapper method that simply calls the getAttribute method
297
     * on the tag of this node.
298
     *
299
     * @param string $key
300
     * @return mixed
301
     */
302
    public function getAttribute($key)
303
    {
304
        $attribute = $this->tag->getAttribute($key);
305
        if ( ! is_null($attribute)) {
306
            $attribute = $attribute['value'];
307
        }
308
309
        return $attribute;
310
    }
311
312
    /**
313
     * A wrapper method that simply calls the hasAttribute method
314
     * on the tag of this node.
315
     *
316
     * @param string $key
317
     * @return bool
318
     */
319
    public function hasAttribute($key)
320
    {
321
        return $this->tag->hasAttribute($key);
322
    }
323
324
    /**
325
     * A wrapper method that simply calls the setAttribute method
326
     * on the tag of this node.
327
     *
328
     * @param string $key
329
     * @param string $value
330
     * @return $this
331
     */
332
    public function setAttribute($key, $value)
333
    {
334
        $this->tag->setAttribute($key, $value);
335
336
        //clear any cache
337
        $this->clear();
338
339
        return $this;
340
    }
341
342
    /**
343
     * A wrapper method that simply calls the removeAttribute method
344
     * on the tag of this node.
345
     *
346
     * @param string $key
347
     * @return void
348
     */
349
    public function removeAttribute($key)
350
    {
351
        $this->tag->removeAttribute($key);
352
353
        //clear any cache
354
        $this->clear();
355
    }
356
357
    /**
358
     * A wrapper method that simply calls the removeAllAttributes
359
     * method on the tag of this node.
360
     *
361
     * @return void
362
     */
363
    public function removeAllAttributes()
364
    {
365
        $this->tag->removeAllAttributes();
366
367
        //clear any cache
368
        $this->clear();
369
    }
370
    /**
371
     * Function to locate a specific ancestor tag in the path to the root.
372
     *
373
     * @param  string $tag
374
     * @return AbstractNode
375
     * @throws ParentNotFoundException
376
     */
377
    public function ancestorByTag($tag)
378
    {
379
        // Start by including ourselves in the comparison.
380
        $node = $this;
381
382
        while ( ! is_null($node)) {
383
            if ($node->tag->name() == $tag) {
384
                return $node;
385
            }
386
387
            $node = $node->getParent();
388
        }
389
390
        throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
391
    }
392
393
    /**
394
     * Find elements by css selector
395
     *
396
     * @param string $selector
397
     * @param int $nth
398
     * @return array|AbstractNode
399
     */
400
    public function find($selector, $nth = null)
401
    {
402
        $selector = new Selector($selector);
403
        $nodes    = $selector->find($this);
404
405
        if ( ! is_null($nth)) {
406
            // return nth-element or array
407
            if (isset($nodes[$nth])) {
408
                return $nodes[$nth];
409
            }
410
411
            return null;
412
        }
413
414
        return $nodes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $nodes; (PHPHtmlParser\Dom\Collection) is incompatible with the return type documented by PHPHtmlParser\Dom\AbstractNode::find of type array|PHPHtmlParser\Dom\AbstractNode.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
415
    }
416
417
    /**
418
     * Find node by id
419
     *
420
     * @param $id
421
     * @return bool|AbstractNode
422
     */
423
    public function findById($id)
424
    {
425
        $finder= new Finder($id);
426
427
        return $finder->find($this);
428
    }
429
430
431
    /**
432
     * Gets the inner html of this node.
433
     *
434
     * @return string
435
     */
436
    abstract public function innerHtml();
437
438
    /**
439
     * Gets the html of this node, including it's own
440
     * tag.
441
     *
442
     * @return string
443
     */
444
    abstract public function outerHtml();
445
446
    /**
447
     * Gets the text of this node (if there is any text).
448
     *
449
     * @return string
450
     */
451
    abstract public function text();
452
453
    /**
454
     * Call this when something in the node tree has changed. Like a child has been added
455
     * or a parent has been changed.
456
     *
457
     * @return void
458
     */
459
    abstract protected function clear();
460
461
    /**
462
     * Check is node type textNode
463
     *
464
     * @return boolean
465
     */
466
    public function isTextNode() {
467
468
        return false;
469
    }
470
}
471