Completed
Push — master ( 061358...056c4c )
by Lars
01:53
created

SimpleXmlDom::lastChild()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 11
loc 11
ccs 0
cts 5
cp 0
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * @noinspection PhpHierarchyChecksInspection
9
 *
10
 * {@inheritdoc}
11
 */
12
class SimpleXmlDom extends AbstractSimpleXmlDom implements \IteratorAggregate, SimpleXmlDomInterface
13
{
14
    /**
15
     * @param \DOMElement|\DOMNode $node
16
     */
17 1
    public function __construct(\DOMNode $node)
18
    {
19 1
        $this->node = $node;
20 1
    }
21
22
    /**
23
     * @param string $name
24
     * @param array  $arguments
25
     *
26
     * @throws \BadMethodCallException
27
     *
28
     * @return SimpleXmlDomInterface|string|null
29
     */
30
    public function __call($name, $arguments)
31
    {
32
        $name = \strtolower($name);
33
34
        if (isset(self::$functionAliases[$name])) {
35
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
36
        }
37
38
        throw new \BadMethodCallException('Method does not exist');
39
    }
40
41
    /**
42
     * Find list of nodes with a CSS selector.
43
     *
44
     * @param string   $selector
45
     * @param int|null $idx
46
     *
47
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
48
     */
49
    public function find(string $selector, $idx = null)
50
    {
51
        return $this->getXmlDomParser()->find($selector, $idx);
52
    }
53
54
    /**
55
     * Returns an array of attributes.
56
     *
57
     * @return array|null
58
     */
59 View Code Duplication
    public function getAllAttributes()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
60
    {
61
        if ($this->node->hasAttributes()) {
62
            $attributes = [];
63
            foreach ($this->node->attributes as $attr) {
64
                $attributes[$attr->name] = XmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
65
            }
66
67
            return $attributes;
68
        }
69
70
        return null;
71
    }
72
73
    /**
74
     * @return bool
75
     */
76
    public function hasAttributes(): bool
77
    {
78
        return $this->node->hasAttributes();
79
    }
80
81
    /**
82
     * Return attribute value.
83
     *
84
     * @param string $name
85
     *
86
     * @return string
87
     */
88
    public function getAttribute(string $name): string
89
    {
90
        if ($this->node instanceof \DOMElement) {
91
            return XmlDomParser::putReplacedBackToPreserveHtmlEntities(
92
                $this->node->getAttribute($name)
93
            );
94
        }
95
96
        return '';
97
    }
98
99
    /**
100
     * Determine if an attribute exists on the element.
101
     *
102
     * @param string $name
103
     *
104
     * @return bool
105
     */
106
    public function hasAttribute(string $name): bool
107
    {
108
        if (!$this->node instanceof \DOMElement) {
109
            return false;
110
        }
111
112
        return $this->node->hasAttribute($name);
113
    }
114
115
    /**
116
     * Get dom node's inner html.
117
     *
118
     * @param bool $multiDecodeNewHtmlEntity
119
     *
120
     * @return string
121
     */
122
    public function innerXml(bool $multiDecodeNewHtmlEntity = false): string
123
    {
124
        return $this->getXmlDomParser()->innerXml($multiDecodeNewHtmlEntity);
125
    }
126
127
    /**
128
     * Remove attribute.
129
     *
130
     * @param string $name <p>The name of the html-attribute.</p>
131
     *
132
     * @return SimpleXmlDomInterface
133
     */
134
    public function removeAttribute(string $name): SimpleXmlDomInterface
135
    {
136
        if (\method_exists($this->node, 'removeAttribute')) {
137
            $this->node->removeAttribute($name);
0 ignored issues
show
Bug introduced by
The method removeAttribute does only exist in DOMElement, but not in DOMNode.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
138
        }
139
140
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::removeAttribute of type self.

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...
141
    }
142
143
    /**
144
     * Replace child node.
145
     *
146
     * @param string $string
147
     *
148
     * @return SimpleXmlDomInterface
149
     */
150 View Code Duplication
    protected function replaceChildWithString(string $string): SimpleXmlDomInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    {
152
        if (!empty($string)) {
153
            $newDocument = new XmlDomParser($string);
154
155
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
156
            $tmpStr = $this->normalizeStringForComparision($string);
157
            if ($tmpDomString !== $tmpStr) {
158
                throw new \RuntimeException(
159
                    'Not valid HTML fragment!' . "\n" .
160
                    $tmpDomString . "\n" .
161
                    $tmpStr
162
                );
163
            }
164
        }
165
166
        /** @var \DOMNode[] $remove_nodes */
167
        $remove_nodes = [];
168
        if (
169
            $this->node->childNodes
170
            &&
171
            $this->node->childNodes->length > 0
172
        ) {
173
            // INFO: We need to fetch the nodes first, before we can delete them, because of missing references in the dom,
174
            // if we delete the elements on the fly.
175
            foreach ($this->node->childNodes as $node) {
176
                $remove_nodes[] = $node;
177
            }
178
        }
179
        foreach ($remove_nodes as $remove_node) {
180
            $this->node->removeChild($remove_node);
181
        }
182
183
        if (!empty($newDocument)) {
184
            $ownerDocument = $this->node->ownerDocument;
185
            if (
186
                $ownerDocument !== null
187
                &&
188
                $newDocument->getDocument()->documentElement !== null
189
            ) {
190
                $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
191
                /** @noinspection UnusedFunctionResultInspection */
192
                $this->node->appendChild($newNode);
193
            }
194
        }
195
196
        return $this;
197
    }
198
199
    /**
200
     * Replace this node.
201
     *
202
     * @param string $string
203
     *
204
     * @return SimpleXmlDomInterface
205
     */
206
    protected function replaceNodeWithString(string $string): SimpleXmlDomInterface
207
    {
208
        if (empty($string)) {
209
            $this->node->parentNode->removeChild($this->node);
210
211
            return $this;
212
        }
213
214
        $newDocument = new XmlDomParser($string);
215
216
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
217
        $tmpStr = $this->normalizeStringForComparision($string);
218 View Code Duplication
        if ($tmpDomOuterTextString !== $tmpStr) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219
            throw new \RuntimeException(
220
                'Not valid HTML fragment!' . "\n"
221
                . $tmpDomOuterTextString . "\n" .
222
                $tmpStr
223
            );
224
        }
225
226
        $ownerDocument = $this->node->ownerDocument;
227
        if (
228
            $ownerDocument === null
229
            ||
230
            $newDocument->getDocument()->documentElement === null
231
        ) {
232
            return $this;
233
        }
234
235
        $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
236
237
        $this->node->parentNode->replaceChild($newNode, $this->node);
238
        $this->node = $newNode;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Replace this node with text
245
     *
246
     * @param string $string
247
     *
248
     * @return SimpleXmlDomInterface
249
     */
250 View Code Duplication
    protected function replaceTextWithString($string): SimpleXmlDomInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
    {
252
        if (empty($string)) {
253
            $this->node->parentNode->removeChild($this->node);
254
255
            return $this;
256
        }
257
258
        $ownerDocument = $this->node->ownerDocument;
259
        if ($ownerDocument !== null) {
260
            $newElement = $ownerDocument->createTextNode($string);
261
            $newNode = $ownerDocument->importNode($newElement, true);
262
            $this->node->parentNode->replaceChild($newNode, $this->node);
263
            $this->node = $newNode;
264
        }
265
266
        return $this;
267
    }
268
269
    /**
270
     * Set attribute value.
271
     *
272
     * @param string      $name       <p>The name of the html-attribute.</p>
273
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
274
     * @param bool        $strict     </p>
275
     *                                $value must be NULL, to remove the attribute,
276
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
277
     *                                </p>
278
     *
279
     * @return SimpleXmlDomInterface
280
     */
281 View Code Duplication
    public function setAttribute(string $name, $value = null, bool $strict = false): SimpleXmlDomInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        if (
284
            ($strict && $value === null)
285
            ||
286
            (!$strict && empty($value))
287
        ) {
288
            /** @noinspection UnusedFunctionResultInspection */
289
            $this->removeAttribute($name);
290
        } elseif (\method_exists($this->node, 'setAttribute')) {
291
            /** @noinspection UnusedFunctionResultInspection */
292
            $this->node->setAttribute($name, $value);
0 ignored issues
show
Bug introduced by
The method setAttribute does only exist in DOMElement, but not in DOMNode.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
293
        }
294
295
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::setAttribute of type self.

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...
296
    }
297
298
    /**
299
     * Get dom node's plain text.
300
     *
301
     * @return string
302
     */
303
    public function text(): string
304
    {
305
        return $this->getXmlDomParser()->fixHtmlOutput($this->node->textContent);
306
    }
307
308
    /**
309
     * Get dom node's outer html.
310
     *
311
     * @param bool $multiDecodeNewHtmlEntity
312
     *
313
     * @return string
314
     */
315
    public function xml(bool $multiDecodeNewHtmlEntity = false): string
316
    {
317
        return $this->getXmlDomParser()->xml($multiDecodeNewHtmlEntity, false);
318
    }
319
320
    /**
321
     * Change the name of a tag in a "DOMNode".
322
     *
323
     * @param \DOMNode $node
324
     * @param string   $name
325
     *
326
     * @return \DOMElement|false
327
     *                          <p>DOMElement a new instance of class DOMElement or false
328
     *                          if an error occured.</p>
329
     */
330 View Code Duplication
    protected function changeElementName(\DOMNode $node, string $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
331
    {
332
        $ownerDocument = $node->ownerDocument;
333
        if ($ownerDocument) {
334
            $newNode = $ownerDocument->createElement($name);
335
        } else {
336
            return false;
337
        }
338
339
        foreach ($node->childNodes as $child) {
340
            $child = $ownerDocument->importNode($child, true);
341
            /** @noinspection UnusedFunctionResultInspection */
342
            $newNode->appendChild($child);
343
        }
344
345
        foreach ($node->attributes as $attrName => $attrNode) {
346
            /** @noinspection UnusedFunctionResultInspection */
347
            $newNode->setAttribute($attrName, $attrNode);
348
        }
349
350
        /** @noinspection UnusedFunctionResultInspection */
351
        $newNode->ownerDocument->replaceChild($newNode, $node);
352
353
        return $newNode;
354
    }
355
356
    /**
357
     * Returns children of node.
358
     *
359
     * @param int $idx
360
     *
361
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface|null
362
     */
363 View Code Duplication
    public function childNodes(int $idx = -1)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
364
    {
365
        $nodeList = $this->getIterator();
366
367
        if ($idx === -1) {
368
            return $nodeList;
369
        }
370
371
        return $nodeList[$idx] ?? null;
372
    }
373
374
    /**
375
     * Find nodes with a CSS selector.
376
     *
377
     * @param string $selector
378
     *
379
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
380
     */
381
    public function findMulti(string $selector): SimpleXmlDomNodeInterface
382
    {
383
        return $this->getXmlDomParser()->findMulti($selector);
384
    }
385
386
    /**
387
     * Find nodes with a CSS selector.
388
     *
389
     * @param string $selector
390
     *
391
     * @return false|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
392
     */
393
    public function findMultiOrFalse(string $selector)
394
    {
395
        return $this->getXmlDomParser()->findMultiOrFalse($selector);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getXmlDomParser()...ultiOrFalse($selector); of type false|voku\helper\Simple...mpleXmlDomNodeInterface adds the type voku\helper\SimpleXmlDomInterface to the return on line 395 which is incompatible with the return type declared by the interface voku\helper\SimpleXmlDom...rface::findMultiOrFalse of type false|voku\helper\Simple...mpleXmlDomNodeInterface.
Loading history...
396
    }
397
398
    /**
399
     * Find one node with a CSS selector.
400
     *
401
     * @param string $selector
402
     *
403
     * @return SimpleXmlDomInterface
404
     */
405
    public function findOne(string $selector): SimpleXmlDomInterface
406
    {
407
        return $this->getXmlDomParser()->findOne($selector);
408
    }
409
410
    /**
411
     * Find one node with a CSS selector or false, if no element is found.
412
     *
413
     * @param string $selector
414
     *
415
     * @return false|SimpleXmlDomInterface
416
     */
417
    public function findOneOrFalse(string $selector)
418
    {
419
        return $this->getXmlDomParser()->findOneOrFalse($selector);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getXmlDomP...dOneOrFalse($selector); (false|voku\helper\Simple...mpleXmlDomNodeInterface) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::findOneOrFalse of type false|voku\helper\SimpleXmlDomInterface.

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...
420
    }
421
422
    /**
423
     * Returns the first child of node.
424
     *
425
     * @return SimpleXmlDomInterface|null
426
     */
427 View Code Duplication
    public function firstChild()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
    {
429
        /** @var \DOMNode|null $node */
430
        $node = $this->node->firstChild;
431
432
        if ($node === null) {
433
            return null;
434
        }
435
436
        return new static($node);
437
    }
438
439
    /**
440
     * Return elements by ".class".
441
     *
442
     * @param string $class
443
     *
444
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
445
     */
446
    public function getElementByClass(string $class): SimpleXmlDomNodeInterface
447
    {
448
        return $this->findMulti(".${class}");
449
    }
450
451
    /**
452
     * Return element by #id.
453
     *
454
     * @param string $id
455
     *
456
     * @return SimpleXmlDomInterface
457
     */
458
    public function getElementById(string $id): SimpleXmlDomInterface
459
    {
460
        return $this->findOne("#${id}");
461
    }
462
463
    /**
464
     * Return element by tag name.
465
     *
466
     * @param string $name
467
     *
468
     * @return SimpleXmlDomInterface
469
     */
470 View Code Duplication
    public function getElementByTagName(string $name): SimpleXmlDomInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
471
    {
472
        if ($this->node instanceof \DOMElement) {
473
            $node = $this->node->getElementsByTagName($name)->item(0);
474
        } else {
475
            $node = null;
476
        }
477
478
        if ($node === null) {
479
            return new SimpleXmlDomBlank();
480
        }
481
482
        return new static($node);
483
    }
484
485
    /**
486
     * Returns elements by "#id".
487
     *
488
     * @param string   $id
489
     * @param int|null $idx
490
     *
491
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
492
     */
493
    public function getElementsById(string $id, $idx = null)
494
    {
495
        return $this->find("#${id}", $idx);
496
    }
497
498
    /**
499
     * Returns elements by tag name.
500
     *
501
     * @param string   $name
502
     * @param int|null $idx
503
     *
504
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
505
     */
506 View Code Duplication
    public function getElementsByTagName(string $name, $idx = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
507
    {
508
        if ($this->node instanceof \DOMElement) {
509
            $nodesList = $this->node->getElementsByTagName($name);
510
        } else {
511
            $nodesList = [];
512
        }
513
514
        $elements = new SimpleXmlDomNode();
515
516
        foreach ($nodesList as $node) {
517
            $elements[] = new static($node);
518
        }
519
520
        // return all elements
521
        if ($idx === null) {
522
            if (\count($elements) === 0) {
523
                return new SimpleXmlDomNodeBlank();
524
            }
525
526
            return $elements;
527
        }
528
529
        // handle negative values
530
        if ($idx < 0) {
531
            $idx = \count($elements) + $idx;
532
        }
533
534
        // return one element
535
        return $elements[$idx] ?? new SimpleXmlDomBlank();
536
    }
537
538
    /**
539
     * @return \DOMNode
540
     */
541 1
    public function getNode(): \DOMNode
542
    {
543 1
        return $this->node;
544
    }
545
546
    /**
547
     * Create a new "XmlDomParser"-object from the current context.
548
     *
549
     * @return XmlDomParser
550
     */
551
    public function getXmlDomParser(): XmlDomParser
552
    {
553
        return new XmlDomParser($this);
554
    }
555
556
    /**
557
     * Get dom node's inner html.
558
     *
559
     * @param bool $multiDecodeNewHtmlEntity
560
     *
561
     * @return string
562
     */
563
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false): string
564
    {
565
        return $this->getXmlDomParser()->innerHtml($multiDecodeNewHtmlEntity);
566
    }
567
568
    /**
569
     * Nodes can get partially destroyed in which they're still an
570
     * actual DOM node (such as \DOMElement) but almost their entire
571
     * body is gone, including the `nodeType` attribute.
572
     *
573
     * @return bool true if node has been destroyed
574
     */
575
    public function isRemoved(): bool
576
    {
577
        return !isset($this->node->nodeType);
578
    }
579
580
    /**
581
     * Returns the last child of node.
582
     *
583
     * @return SimpleXmlDomInterface|null
584
     */
585 View Code Duplication
    public function lastChild()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
586
    {
587
        /** @var \DOMNode|null $node */
588
        $node = $this->node->lastChild;
589
590
        if ($node === null) {
591
            return null;
592
        }
593
594
        return new static($node);
595
    }
596
597
    /**
598
     * Returns the next sibling of node.
599
     *
600
     * @return SimpleXmlDomInterface|null
601
     */
602 View Code Duplication
    public function nextSibling()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
603
    {
604
        /** @var \DOMNode|null $node */
605
        $node = $this->node->nextSibling;
606
607
        if ($node === null) {
608
            return null;
609
        }
610
611
        return new static($node);
612
    }
613
614
    /**
615
     * Returns the parent of node.
616
     *
617
     * @return SimpleXmlDomInterface
618
     */
619
    public function parentNode(): SimpleXmlDomInterface
620
    {
621
        return new static($this->node->parentNode);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new static($this->node->parentNode); (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::parentNode of type self.

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...
622
    }
623
624
    /**
625
     * Returns the previous sibling of node.
626
     *
627
     * @return SimpleXmlDomInterface|null
628
     */
629 View Code Duplication
    public function previousSibling()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
    {
631
        /** @var \DOMNode|null $node */
632
        $node = $this->node->previousSibling;
633
634
        if ($node === null) {
635
            return null;
636
        }
637
638
        return new static($node);
639
    }
640
641
    /**
642
     * @param string|string[]|null $value <p>
643
     *                                    null === get the current input value
644
     *                                    text === set a new input value
645
     *                                    </p>
646
     *
647
     * @return string|string[]|null
648
     */
649 View Code Duplication
    public function val($value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
650
    {
651
        if ($value === null) {
652
            if (
653
                $this->tag === 'input'
0 ignored issues
show
Documentation introduced by
The property tag does not exist on object<voku\helper\SimpleXmlDom>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
654
                &&
655
                (
656
                    $this->getAttribute('type') === 'text'
657
                    ||
658
                    !$this->hasAttribute('type')
659
                )
660
            ) {
661
                return $this->getAttribute('value');
662
            }
663
664
            if (
665
                $this->hasAttribute('checked')
666
                &&
667
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
668
            ) {
669
                return $this->getAttribute('value');
670
            }
671
672
            if ($this->node->nodeName === 'select') {
673
                $valuesFromDom = [];
674
                $options = $this->getElementsByTagName('option');
675
                if ($options instanceof SimpleXmlDomNode) {
676
                    foreach ($options as $option) {
677
                        if ($this->hasAttribute('checked')) {
678
                            /** @noinspection UnnecessaryCastingInspection */
679
                            $valuesFromDom[] = (string) $option->getAttribute('value');
680
                        }
681
                    }
682
                }
683
684
                if (\count($valuesFromDom) === 0) {
685
                    return null;
686
                }
687
688
                return $valuesFromDom;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $valuesFromDom; (array) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::val of type string|string[]|null.

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...
689
            }
690
691
            if ($this->node->nodeName === 'textarea') {
692
                return $this->node->nodeValue;
693
            }
694
        } else {
695
            /** @noinspection NestedPositiveIfStatementsInspection */
696
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
697
                if ($value === $this->getAttribute('value')) {
698
                    /** @noinspection UnusedFunctionResultInspection */
699
                    $this->setAttribute('checked', 'checked');
700
                } else {
701
                    /** @noinspection UnusedFunctionResultInspection */
702
                    $this->removeAttribute('checked');
703
                }
704
            } elseif ($this->node instanceof \DOMElement && $this->node->nodeName === 'select') {
705
                foreach ($this->node->getElementsByTagName('option') as $option) {
706
                    /** @var \DOMElement $option */
707
                    if ($value === $option->getAttribute('value')) {
708
                        /** @noinspection UnusedFunctionResultInspection */
709
                        $option->setAttribute('selected', 'selected');
710
                    } else {
711
                        $option->removeAttribute('selected');
712
                    }
713
                }
714
            } elseif ($this->node->nodeName === 'input' && \is_string($value)) {
715
                // Set value for input elements
716
                /** @noinspection UnusedFunctionResultInspection */
717
                $this->setAttribute('value', $value);
718
            } elseif ($this->node->nodeName === 'textarea' && \is_string($value)) {
719
                $this->node->nodeValue = $value;
720
            }
721
        }
722
723
        return null;
724
    }
725
726
    /**
727
     * Retrieve an external iterator.
728
     *
729
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
730
     *
731
     * @return SimpleXmlDomNode
732
     *                           <p>
733
     *                              An instance of an object implementing <b>Iterator</b> or
734
     *                              <b>Traversable</b>
735
     *                           </p>
736
     */
737 View Code Duplication
    public function getIterator(): SimpleXmlDomNodeInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
738
    {
739
        $elements = new SimpleXmlDomNode();
740
        if ($this->node->hasChildNodes()) {
741
            foreach ($this->node->childNodes as $node) {
742
                $elements[] = new static($node);
743
            }
744
        }
745
746
        return $elements;
747
    }
748
749
    /**
750
     * Normalize the given input for comparision.
751
     *
752
     * @param string|XmlDomParser $input
753
     *
754
     * @return string
755
     */
756
    private function normalizeStringForComparision($input): string
757
    {
758
        if ($input instanceof XmlDomParser) {
759
            $string = $input->plaintext;
760
        } else {
761
            $string = (string) $input;
762
        }
763
764
        return
765
            \urlencode(
766
                \urldecode(
767
                    \trim(
768
                        \str_replace(
769
                            [
770
                                ' ',
771
                                "\n",
772
                                "\r",
773
                                '/>',
774
                            ],
775
                            [
776
                                '',
777
                                '',
778
                                '',
779
                                '>',
780
                            ],
781
                            \strtolower($string)
782
                        )
783
                    )
784
                )
785
            );
786
    }
787
}
788