Completed
Push — master ( ae518a...b71ea1 )
by Lars
01:46
created

SimpleXmlDom::nextNonWhitespaceSibling()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 16
loc 16
ccs 0
cts 7
cp 0
crap 20
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * @noinspection PhpHierarchyChecksInspection
9
 *
10
 * {@inheritdoc}
11
 *
12
 * @implements \IteratorAggregate<int, \DOMNode>
13
 */
14
class SimpleXmlDom extends AbstractSimpleXmlDom implements \IteratorAggregate, SimpleXmlDomInterface
15
{
16
    /**
17
     * @param \DOMElement|\DOMNode $node
18
     */
19 1
    public function __construct(\DOMNode $node)
20
    {
21 1
        $this->node = $node;
22 1
    }
23
24
    /**
25
     * @param string $name
26
     * @param array  $arguments
27
     *
28
     * @throws \BadMethodCallException
29
     *
30
     * @return SimpleXmlDomInterface|string|null
31
     */
32
    public function __call($name, $arguments)
33
    {
34
        $name = \strtolower($name);
35
36
        if (isset(self::$functionAliases[$name])) {
37
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
38
        }
39
40
        throw new \BadMethodCallException('Method does not exist');
41
    }
42
43
    /**
44
     * Find list of nodes with a CSS selector.
45
     *
46
     * @param string   $selector
47
     * @param int|null $idx
48
     *
49
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
50
     */
51
    public function find(string $selector, $idx = null)
52
    {
53
        return $this->getXmlDomParser()->find($selector, $idx);
54
    }
55
56
    /**
57
     * Returns an array of attributes.
58
     *
59
     * @return string[]|null
60
     */
61 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...
62
    {
63
        if (
64
            $this->node
65
            &&
66
            $this->node->hasAttributes()
67
        ) {
68
            $attributes = [];
69
            foreach ($this->node->attributes ?? [] as $attr) {
70
                $attributes[$attr->name] = XmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
71
            }
72
73
            return $attributes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $attributes; (array) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDom...rface::getAllAttributes of type 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...
74
        }
75
76
        return null;
77
    }
78
79
    /**
80
     * @return bool
81
     */
82
    public function hasAttributes(): bool
83
    {
84
        return $this->node->hasAttributes();
85
    }
86
87
    /**
88
     * Return attribute value.
89
     *
90
     * @param string $name
91
     *
92
     * @return string
93
     */
94
    public function getAttribute(string $name): string
95
    {
96
        if ($this->node instanceof \DOMElement) {
97
            return XmlDomParser::putReplacedBackToPreserveHtmlEntities(
98
                $this->node->getAttribute($name)
99
            );
100
        }
101
102
        return '';
103
    }
104
105
    /**
106
     * Determine if an attribute exists on the element.
107
     *
108
     * @param string $name
109
     *
110
     * @return bool
111
     */
112
    public function hasAttribute(string $name): bool
113
    {
114
        if (!$this->node instanceof \DOMElement) {
115
            return false;
116
        }
117
118
        return $this->node->hasAttribute($name);
119
    }
120
121
    /**
122
     * Get dom node's inner html.
123
     *
124
     * @param bool $multiDecodeNewHtmlEntity
125
     *
126
     * @return string
127
     */
128
    public function innerXml(bool $multiDecodeNewHtmlEntity = false): string
129
    {
130
        return $this->getXmlDomParser()->innerXml($multiDecodeNewHtmlEntity);
131
    }
132
133
    /**
134
     * Remove attribute.
135
     *
136
     * @param string $name <p>The name of the html-attribute.</p>
137
     *
138
     * @return SimpleXmlDomInterface
139
     */
140
    public function removeAttribute(string $name): SimpleXmlDomInterface
141
    {
142
        if (\method_exists($this->node, 'removeAttribute')) {
143
            $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...
144
        }
145
146
        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...
147
    }
148
149
    /**
150
     * Replace child node.
151
     *
152
     * @param string $string
153
     *
154
     * @return SimpleXmlDomInterface
155
     */
156
    protected function replaceChildWithString(string $string): SimpleXmlDomInterface
157
    {
158
        if (!empty($string)) {
159
            $newDocument = new XmlDomParser($string);
160
161
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
162
            $tmpStr = $this->normalizeStringForComparision($string);
163
            if ($tmpDomString !== $tmpStr) {
164
                throw new \RuntimeException(
165
                    'Not valid HTML fragment!' . "\n" .
166
                    $tmpDomString . "\n" .
167
                    $tmpStr
168
                );
169
            }
170
        }
171
172
        /** @var \DOMNode[] $remove_nodes */
173
        $remove_nodes = [];
174
        if ($this->node->childNodes->length > 0) {
175
            // INFO: We need to fetch the nodes first, before we can delete them, because of missing references in the dom,
176
            // if we delete the elements on the fly.
177
            foreach ($this->node->childNodes as $node) {
178
                $remove_nodes[] = $node;
179
            }
180
        }
181
        foreach ($remove_nodes as $remove_node) {
182
            $this->node->removeChild($remove_node);
183
        }
184
185
        if (!empty($newDocument)) {
186
            $ownerDocument = $this->node->ownerDocument;
187
            if (
188
                $ownerDocument
189
                &&
190
                $newDocument->getDocument()->documentElement
191
            ) {
192
                $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
193
                /** @noinspection UnusedFunctionResultInspection */
194
                $this->node->appendChild($newNode);
195
            }
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * Replace this node.
203
     *
204
     * @param string $string
205
     *
206
     * @return SimpleXmlDomInterface
207
     */
208
    protected function replaceNodeWithString(string $string): SimpleXmlDomInterface
209
    {
210
        if (empty($string)) {
211
            $this->node->parentNode->removeChild($this->node);
212
213
            return $this;
214
        }
215
216
        $newDocument = new XmlDomParser($string);
217
218
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
219
        $tmpStr = $this->normalizeStringForComparision($string);
220 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...
221
            throw new \RuntimeException(
222
                'Not valid HTML fragment!' . "\n"
223
                . $tmpDomOuterTextString . "\n" .
224
                $tmpStr
225
            );
226
        }
227
228
        $ownerDocument = $this->node->ownerDocument;
229
        if (
230
            $ownerDocument === null
231
            ||
232
            $newDocument->getDocument()->documentElement === null
233
        ) {
234
            return $this;
235
        }
236
237
        $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
238
239
        $this->node->parentNode->replaceChild($newNode, $this->node);
240
        $this->node = $newNode;
241
242
        return $this;
243
    }
244
245
    /**
246
     * Replace this node with text
247
     *
248
     * @param string $string
249
     *
250
     * @return SimpleXmlDomInterface
251
     */
252 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...
253
    {
254
        if (empty($string)) {
255
            $this->node->parentNode->removeChild($this->node);
256
257
            return $this;
258
        }
259
260
        $ownerDocument = $this->node->ownerDocument;
261
        if ($ownerDocument) {
262
            $newElement = $ownerDocument->createTextNode($string);
263
            $newNode = $ownerDocument->importNode($newElement, true);
264
            $this->node->parentNode->replaceChild($newNode, $this->node);
265
            $this->node = $newNode;
266
        }
267
268
        return $this;
269
    }
270
271
    /**
272
     * Set attribute value.
273
     *
274
     * @param string      $name       <p>The name of the html-attribute.</p>
275
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
276
     * @param bool        $strict     </p>
277
     *                                $value must be NULL, to remove the attribute,
278
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
279
     *                                </p>
280
     *
281
     * @return SimpleXmlDomInterface
282
     */
283 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...
284
    {
285
        if (
286
            ($strict && $value === null)
287
            ||
288
            (!$strict && empty($value))
289
        ) {
290
            /** @noinspection UnusedFunctionResultInspection */
291
            $this->removeAttribute($name);
292
        } elseif (\method_exists($this->node, 'setAttribute')) {
293
            /** @noinspection UnusedFunctionResultInspection */
294
            $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...
295
        }
296
297
        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...
298
    }
299
300
    /**
301
     * Get dom node's plain text.
302
     *
303
     * @return string
304
     */
305
    public function text(): string
306
    {
307
        return $this->getXmlDomParser()->fixHtmlOutput($this->node->textContent);
308
    }
309
310
    /**
311
     * Get dom node's outer html.
312
     *
313
     * @param bool $multiDecodeNewHtmlEntity
314
     *
315
     * @return string
316
     */
317
    public function xml(bool $multiDecodeNewHtmlEntity = false): string
318
    {
319
        return $this->getXmlDomParser()->xml($multiDecodeNewHtmlEntity, false);
320
    }
321
322
    /**
323
     * Change the name of a tag in a "DOMNode".
324
     *
325
     * @param \DOMNode $node
326
     * @param string   $name
327
     *
328
     * @return \DOMElement|false
329
     *                          <p>DOMElement a new instance of class DOMElement or false
330
     *                          if an error occured.</p>
331
     */
332 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...
333
    {
334
        $ownerDocument = $node->ownerDocument;
335
        if (!$ownerDocument) {
336
            return false;
337
        }
338
339
        $newNode = $ownerDocument->createElement($name);
340
341
        foreach ($node->childNodes as $child) {
342
            $child = $ownerDocument->importNode($child, true);
343
            $newNode->appendChild($child);
344
        }
345
346
        foreach ($node->attributes ?? [] as $attrName => $attrNode) {
347
            /** @noinspection UnusedFunctionResultInspection */
348
            $newNode->setAttribute($attrName, $attrNode);
349
        }
350
351
        if ($newNode->ownerDocument) {
352
            /** @noinspection UnusedFunctionResultInspection */
353
            $newNode->ownerDocument->replaceChild($newNode, $node);
354
        }
355
356
        return $newNode;
357
    }
358
359
    /**
360
     * Returns children of node.
361
     *
362
     * @param int $idx
363
     *
364
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>|null
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...leXmlDomInterface>|null could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
365
     */
366 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...
367
    {
368
        $nodeList = $this->getIterator();
369
370
        if ($idx === -1) {
371
            return $nodeList;
372
        }
373
374
        return $nodeList[$idx] ?? null;
375
    }
376
377
    /**
378
     * Find nodes with a CSS selector.
379
     *
380
     * @param string $selector
381
     *
382
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface[]|...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 49. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
383
     */
384
    public function findMulti(string $selector): SimpleXmlDomNodeInterface
385
    {
386
        return $this->getXmlDomParser()->findMulti($selector);
387
    }
388
389
    /**
390
     * Find nodes with a CSS selector.
391
     *
392
     * @param string $selector
393
     *
394
     * @return false|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type false|SimpleXmlDomInterf...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 55. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
395
     */
396
    public function findMultiOrFalse(string $selector)
397
    {
398
        return $this->getXmlDomParser()->findMultiOrFalse($selector);
399
    }
400
401
    /**
402
     * Find one node with a CSS selector.
403
     *
404
     * @param string $selector
405
     *
406
     * @return SimpleXmlDomInterface
407
     */
408
    public function findOne(string $selector): SimpleXmlDomInterface
409
    {
410
        return $this->getXmlDomParser()->findOne($selector);
411
    }
412
413
    /**
414
     * Find one node with a CSS selector or false, if no element is found.
415
     *
416
     * @param string $selector
417
     *
418
     * @return false|SimpleXmlDomInterface
419
     */
420
    public function findOneOrFalse(string $selector)
421
    {
422
        return $this->getXmlDomParser()->findOneOrFalse($selector);
423
    }
424
425
    /**
426
     * Returns the first child of node.
427
     *
428
     * @return SimpleXmlDomInterface|null
429
     */
430 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...
431
    {
432
        /** @var \DOMNode|null $node */
433
        $node = $this->node->firstChild;
434
435
        if ($node === null) {
436
            return null;
437
        }
438
439
        return new static($node);
440
    }
441
442
    /**
443
     * Return elements by ".class".
444
     *
445
     * @param string $class
446
     *
447
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface[]|...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 49. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
448
     */
449
    public function getElementByClass(string $class): SimpleXmlDomNodeInterface
450
    {
451
        return $this->findMulti(".${class}");
452
    }
453
454
    /**
455
     * Return element by #id.
456
     *
457
     * @param string $id
458
     *
459
     * @return SimpleXmlDomInterface
460
     */
461
    public function getElementById(string $id): SimpleXmlDomInterface
462
    {
463
        return $this->findOne("#${id}");
464
    }
465
466
    /**
467
     * Return element by tag name.
468
     *
469
     * @param string $name
470
     *
471
     * @return SimpleXmlDomInterface
472
     */
473 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...
474
    {
475
        if ($this->node instanceof \DOMElement) {
476
            $node = $this->node->getElementsByTagName($name)->item(0);
477
        } else {
478
            $node = null;
479
        }
480
481
        if ($node === null) {
482
            return new SimpleXmlDomBlank();
483
        }
484
485
        return new static($node);
486
    }
487
488
    /**
489
     * Returns elements by "#id".
490
     *
491
     * @param string   $id
492
     * @param int|null $idx
493
     *
494
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
495
     */
496
    public function getElementsById(string $id, $idx = null)
497
    {
498
        return $this->find("#${id}", $idx);
499
    }
500
501
    /**
502
     * Returns elements by tag name.
503
     *
504
     * @param string   $name
505
     * @param int|null $idx
506
     *
507
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
508
     */
509 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...
510
    {
511
        if ($this->node instanceof \DOMElement) {
512
            $nodesList = $this->node->getElementsByTagName($name);
513
        } else {
514
            $nodesList = [];
515
        }
516
517
        $elements = new SimpleXmlDomNode();
518
519
        foreach ($nodesList as $node) {
520
            $elements[] = new static($node);
521
        }
522
523
        // return all elements
524
        if ($idx === null) {
525
            if (\count($elements) === 0) {
526
                return new SimpleXmlDomNodeBlank();
527
            }
528
529
            return $elements;
530
        }
531
532
        // handle negative values
533
        if ($idx < 0) {
534
            $idx = \count($elements) + $idx;
535
        }
536
537
        // return one element
538
        return $elements[$idx] ?? new SimpleXmlDomBlank();
539
    }
540
541
    /**
542
     * @return \DOMNode
543
     */
544 1
    public function getNode(): \DOMNode
545
    {
546 1
        return $this->node;
547
    }
548
549
    /**
550
     * Create a new "XmlDomParser"-object from the current context.
551
     *
552
     * @return XmlDomParser
553
     */
554
    public function getXmlDomParser(): XmlDomParser
555
    {
556
        return new XmlDomParser($this);
557
    }
558
559
    /**
560
     * Get dom node's inner html.
561
     *
562
     * @param bool $multiDecodeNewHtmlEntity
563
     *
564
     * @return string
565
     */
566
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false): string
567
    {
568
        return $this->getXmlDomParser()->innerHtml($multiDecodeNewHtmlEntity);
569
    }
570
571
    /**
572
     * Nodes can get partially destroyed in which they're still an
573
     * actual DOM node (such as \DOMElement) but almost their entire
574
     * body is gone, including the `nodeType` attribute.
575
     *
576
     * @return bool true if node has been destroyed
577
     */
578
    public function isRemoved(): bool
579
    {
580
        return !isset($this->node->nodeType);
581
    }
582
583
    /**
584
     * Returns the last child of node.
585
     *
586
     * @return SimpleXmlDomInterface|null
587
     */
588 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...
589
    {
590
        /** @var \DOMNode|null $node */
591
        $node = $this->node->lastChild;
592
593
        if ($node === null) {
594
            return null;
595
        }
596
597
        return new static($node);
598
    }
599
600
    /**
601
     * Returns the next sibling of node.
602
     *
603
     * @return SimpleXmlDomInterface|null
604
     */
605 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...
606
    {
607
        /** @var \DOMNode|null $node */
608
        $node = $this->node->nextSibling;
609
610
        if ($node === null) {
611
            return null;
612
        }
613
614
        return new static($node);
615
    }
616
617
    /**
618
     * Returns the next sibling of node.
619
     *
620
     * @return SimpleXmlDomInterface|null
621
     */
622 View Code Duplication
    public function nextNonWhitespaceSibling()
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...
623
    {
624
        /** @var \DOMNode|null $node */
625
        $node = $this->node->nextSibling;
626
627
        if ($node === null) {
628
            return null;
629
        }
630
631
        while ($node && !\trim($node->textContent)) {
632
            /** @var \DOMNode|null $node */
633
            $node = $node->nextSibling;
634
        }
635
636
        return new static($node);
0 ignored issues
show
Bug introduced by
It seems like $node can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
637
    }
638
639
    /**
640
     * Returns the parent of node.
641
     *
642
     * @return SimpleXmlDomInterface
643
     */
644
    public function parentNode(): SimpleXmlDomInterface
645
    {
646
        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...
647
    }
648
649
    /**
650
     * Returns the previous sibling of node.
651
     *
652
     * @return SimpleXmlDomInterface|null
653
     */
654 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...
655
    {
656
        /** @var \DOMNode|null $node */
657
        $node = $this->node->previousSibling;
658
659
        if ($node === null) {
660
            return null;
661
        }
662
663
        return new static($node);
664
    }
665
666
    /**
667
     * @param string|string[]|null $value <p>
668
     *                                    null === get the current input value
669
     *                                    text === set a new input value
670
     *                                    </p>
671
     *
672
     * @return string|string[]|null
673
     */
674 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...
675
    {
676
        if ($value === null) {
677
            if (
678
                $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...
679
                &&
680
                (
681
                    $this->getAttribute('type') === 'hidden'
682
                    ||
683
                    $this->getAttribute('type') === 'text'
684
                    ||
685
                    !$this->hasAttribute('type')
686
                )
687
            ) {
688
                return $this->getAttribute('value');
689
            }
690
691
            if (
692
                $this->hasAttribute('checked')
693
                &&
694
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
695
            ) {
696
                return $this->getAttribute('value');
697
            }
698
699
            if ($this->node->nodeName === 'select') {
700
                $valuesFromDom = [];
701
                $options = $this->getElementsByTagName('option');
702
                if ($options instanceof SimpleXmlDomNode) {
703
                    foreach ($options as $option) {
704
                        if ($this->hasAttribute('checked')) {
705
                            /** @noinspection UnnecessaryCastingInspection */
706
                            $valuesFromDom[] = (string) $option->getAttribute('value');
707
                        }
708
                    }
709
                }
710
711
                if (\count($valuesFromDom) === 0) {
712
                    return null;
713
                }
714
715
                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...
716
            }
717
718
            if ($this->node->nodeName === 'textarea') {
719
                return $this->node->nodeValue;
720
            }
721
        } else {
722
            /** @noinspection NestedPositiveIfStatementsInspection */
723
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
724
                if ($value === $this->getAttribute('value')) {
725
                    /** @noinspection UnusedFunctionResultInspection */
726
                    $this->setAttribute('checked', 'checked');
727
                } else {
728
                    /** @noinspection UnusedFunctionResultInspection */
729
                    $this->removeAttribute('checked');
730
                }
731
            } elseif ($this->node instanceof \DOMElement && $this->node->nodeName === 'select') {
732
                foreach ($this->node->getElementsByTagName('option') as $option) {
733
                    /** @var \DOMElement $option */
734
                    if ($value === $option->getAttribute('value')) {
735
                        /** @noinspection UnusedFunctionResultInspection */
736
                        $option->setAttribute('selected', 'selected');
737
                    } else {
738
                        $option->removeAttribute('selected');
739
                    }
740
                }
741
            } elseif ($this->node->nodeName === 'input' && \is_string($value)) {
742
                // Set value for input elements
743
                /** @noinspection UnusedFunctionResultInspection */
744
                $this->setAttribute('value', $value);
745
            } elseif ($this->node->nodeName === 'textarea' && \is_string($value)) {
746
                $this->node->nodeValue = $value;
747
            }
748
        }
749
750
        return null;
751
    }
752
753
    /**
754
     * Retrieve an external iterator.
755
     *
756
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
757
     *
758
     * @return SimpleXmlDomNode
759
     *                           <p>
760
     *                              An instance of an object implementing <b>Iterator</b> or
761
     *                              <b>Traversable</b>
762
     *                           </p>
763
     */
764 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...
765
    {
766
        $elements = new SimpleXmlDomNode();
767
        if ($this->node->hasChildNodes()) {
768
            foreach ($this->node->childNodes as $node) {
769
                $elements[] = new static($node);
770
            }
771
        }
772
773
        return $elements;
774
    }
775
776
    /**
777
     * Normalize the given input for comparision.
778
     *
779
     * @param string|XmlDomParser $input
780
     *
781
     * @return string
782
     */
783
    private function normalizeStringForComparision($input): string
784
    {
785
        if ($input instanceof XmlDomParser) {
786
            $string = $input->plaintext;
787
        } else {
788
            $string = (string) $input;
789
        }
790
791
        return
792
            \urlencode(
793
                \urldecode(
794
                    \trim(
795
                        \str_replace(
796
                            [
797
                                ' ',
798
                                "\n",
799
                                "\r",
800
                                '/>',
801
                            ],
802
                            [
803
                                '',
804
                                '',
805
                                '',
806
                                '>',
807
                            ],
808
                            \strtolower($string)
809
                        )
810
                    )
811
                )
812
            );
813
    }
814
}
815