Completed
Pull Request — master (#90)
by
unknown
03:17
created

AbstractNode::resetCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
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
                $this->getParent();
0 ignored issues
show
Unused Code introduced by
The call to the method PHPHtmlParser\Dom\AbstractNode::getParent() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
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
    public function resetCount()
116
    {
117
        self::$count = 0;
118
    }
119
120
    /**
121
     * Returns the id of this object.
122
     */
123
    public function id()
124
    {
125
        return $this->id;
126
    }
127
128
    /**
129
     * Returns the parent of node.
130
     *
131
     * @return AbstractNode
132
     */
133
    public function getParent()
134
    {
135
        return $this->parent;
136
    }
137
138
    /**
139
     * Sets the parent node.
140
     *
141
     * @param InnerNode $parent
142
     * @return $this
143
     * @throws CircularException
144
     */
145
    public function setParent(InnerNode $parent)
146
    {
147
        // remove from old parent
148
        if ( ! is_null($this->parent)) {
149
            if ($this->parent->id() == $parent->id()) {
150
                // already the parent
151
                return $this;
152
            }
153
154
            $this->parent->removeChild($this->id);
155
        }
156
157
        $this->parent = $parent;
158
159
        // assign child to parent
160
        $this->parent->addChild($this);
161
162
        //clear any cache
163
        $this->clear();
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
180
        $this->parent = null;
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 setAttribute method
314
     * on the tag of this node.
315
     *
316
     * @param string $key
317
     * @param string $value
318
     * @return $this
319
     */
320
    public function setAttribute($key, $value)
321
    {
322
        $this->tag->setAttribute($key, $value);
323
324
        return $this;
325
    }
326
327
    /**
328
     * A wrapper method that simply calls the removeAttribute method
329
     * on the tag of this node.
330
     *
331
     * @param string $key
332
     * @return void
333
     */
334
    public function removeAttribute($key)
335
    {
336
        $this->tag->removeAttribute($key);
337
    }
338
339
    /**
340
     * A wrapper method that simply calls the removeAllAttributes
341
     * method on the tag of this node.
342
     *
343
     * @return void
344
     */
345
    public function removeAllAttributes()
346
    {
347
        $this->tag->removeAllAttributes();
348
    }
349
350
    /**
351
     * Function to locate a specific ancestor tag in the path to the root.
352
     *
353
     * @param  string $tag
354
     * @return AbstractNode
355
     * @throws ParentNotFoundException
356
     */
357
    public function ancestorByTag($tag)
358
    {
359
        // Start by including ourselves in the comparison.
360
        $node = $this;
361
362
        while ( ! is_null($node)) {
363
            if ($node->tag->name() == $tag) {
364
                return $node;
365
            }
366
367
            $node = $node->getParent();
368
        }
369
370
        throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
371
    }
372
373
    /**
374
     * Find elements by css selector
375
     *
376
     * @param string $selector
377
     * @param int $nth
378
     * @return array|AbstractNode
379
     */
380
    public function find($selector, $nth = null)
381
    {
382
        $selector = new Selector($selector);
383
        $nodes    = $selector->find($this);
384
385
        if ( ! is_null($nth)) {
386
            // return nth-element or array
387
            if (isset($nodes[$nth])) {
388
                return $nodes[$nth];
389
            }
390
391
            return null;
392
        }
393
394
        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...
395
    }
396
397
    /**
398
     * Find node by id
399
     *
400
     * @param $id
401
     * @return bool|AbstractNode
402
     */
403
    public function findById($id)
404
    {
405
        $finder= new Finder($id);
406
407
        return $finder->find($this);
408
    }
409
410
411
    /**
412
     * Gets the inner html of this node.
413
     *
414
     * @return string
415
     */
416
    abstract public function innerHtml();
417
418
    /**
419
     * Gets the html of this node, including it's own
420
     * tag.
421
     *
422
     * @return string
423
     */
424
    abstract public function outerHtml();
425
426
    /**
427
     * Gets the text of this node (if there is any text).
428
     *
429
     * @return string
430
     */
431
    abstract public function text();
432
433
    /**
434
     * Call this when something in the node tree has changed. Like a child has been added
435
     * or a parent has been changed.
436
     *
437
     * @return void
438
     */
439
    abstract protected function clear();
440
441
    /**
442
     * Check is node type textNode
443
     *
444
     * @return boolean
445
     */
446
    public function isTextNode() {
447
448
        return false;
449
    }
450
}
451