Completed
Push — master ( 45d18c...bddea9 )
by Gilles
04:39 queued 01:11
created

AbstractNode::getLength()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 15
rs 8.8571
cc 5
eloc 7
nc 4
nop 3
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Selector;
5
use PHPHtmlParser\Exceptions\CircularException;
6
use PHPHtmlParser\Exceptions\ParentNotFoundException;
7
use stringEncode\Encode;
8
9
/**
10
 * Dom node object.
11
 *
12
 * @property string outerhtml
13
 * @property string innerhtml
14
 * @property string text
15
 * @property \PHPHtmlParser\Dom\Tag tag
16
 * @property InnerNode parent
17
 */
18
abstract class AbstractNode
19
{
20
21
    /**
22
     * Contains the tag name/type
23
     *
24
     * @var \PHPHtmlParser\Dom\Tag
25
     */
26
    protected $tag;
27
28
    /**
29
     * Contains a list of attributes on this tag.
30
     *
31
     * @var array
32
     */
33
    protected $attr = [];
34
35
    /**
36
     * Contains the parent Node.
37
     *
38
     * @var InnerNode
39
     */
40
    protected $parent = null;
41
42
    /**
43
     * The unique id of the class. Given by PHP.
44
     *
45
     * @var string
46
     */
47
    protected $id;
48
49
    /**
50
     * The encoding class used to encode strings.
51
     *
52
     * @var mixed
53
     */
54
    protected $encode;
55
56
    /**
57
     * Creates a unique spl hash for this node.
58
     */
59
    public function __construct()
60
    {
61
        $this->id = spl_object_hash($this);
62
    }
63
64
    /**
65
     * Magic get method for attributes and certain methods.
66
     *
67
     * @param string $key
68
     * @return mixed
69
     */
70
    public function __get($key)
71
    {
72
        // check attribute first
73
        if ( ! is_null($this->getAttribute($key))) {
74
            return $this->getAttribute($key);
75
        }
76
        switch (strtolower($key)) {
77
            case 'outerhtml':
78
                return $this->outerHtml();
79
            case 'innerhtml':
80
                return $this->innerHtml();
81
            case 'text':
82
                return $this->text();
83
            case 'tag':
84
                return $this->getTag();
85
            case 'parent': $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...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

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