Completed
Push — master ( d7231d...c3f5ad )
by Lars
01:56
created

SimpleXmlDom::findMultiOrFalse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
nc 1
cc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/** @noinspection PhpHierarchyChecksInspection */
8
class SimpleXmlDom extends AbstractSimpleXmlDom implements \IteratorAggregate, SimpleXmlDomInterface
9
{
10
    /**
11
     * @param \DOMElement|\DOMNode $node
12
     */
13 1
    public function __construct(\DOMNode $node)
14
    {
15 1
        $this->node = $node;
16 1
    }
17
18
    /**
19
     * @param string $name
20
     * @param array  $arguments
21
     *
22
     * @throws \BadMethodCallException
23
     *
24
     * @return SimpleXmlDomInterface|string|null
25
     */
26
    public function __call($name, $arguments)
27
    {
28
        $name = \strtolower($name);
29
30
        if (isset(self::$functionAliases[$name])) {
31
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
32
        }
33
34
        throw new \BadMethodCallException('Method does not exist');
35
    }
36
37
    /**
38
     * Find list of nodes with a CSS selector.
39
     *
40
     * @param string   $selector
41
     * @param int|null $idx
42
     *
43
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
44
     */
45
    public function find(string $selector, $idx = null)
46
    {
47
        return $this->getXmlDomParser()->find($selector, $idx);
48
    }
49
50
    /**
51
     * Returns an array of attributes.
52
     *
53
     * @return array|null
54
     */
55 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...
56
    {
57
        if ($this->node->hasAttributes()) {
58
            $attributes = [];
59
            foreach ($this->node->attributes as $attr) {
60
                $attributes[$attr->name] = XmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
61
            }
62
63
            return $attributes;
64
        }
65
66
        return null;
67
    }
68
69
    /**
70
     * Return attribute value.
71
     *
72
     * @param string $name
73
     *
74
     * @return string
75
     */
76
    public function getAttribute(string $name): string
77
    {
78
        if ($this->node instanceof \DOMElement) {
79
            return XmlDomParser::putReplacedBackToPreserveHtmlEntities(
80
                $this->node->getAttribute($name)
81
            );
82
        }
83
84
        return '';
85
    }
86
87
    /**
88
     * Determine if an attribute exists on the element.
89
     *
90
     * @param string $name
91
     *
92
     * @return bool
93
     */
94
    public function hasAttribute(string $name): bool
95
    {
96
        if (!$this->node instanceof \DOMElement) {
97
            return false;
98
        }
99
100
        return $this->node->hasAttribute($name);
101
    }
102
103
    /**
104
     * Get dom node's inner html.
105
     *
106
     * @param bool $multiDecodeNewHtmlEntity
107
     *
108
     * @return string
109
     */
110
    public function innerXml(bool $multiDecodeNewHtmlEntity = false): string
111
    {
112
        return $this->getXmlDomParser()->innerXml($multiDecodeNewHtmlEntity);
113
    }
114
115
    /**
116
     * Remove attribute.
117
     *
118
     * @param string $name <p>The name of the html-attribute.</p>
119
     *
120
     * @return SimpleXmlDomInterface
121
     */
122
    public function removeAttribute(string $name): SimpleXmlDomInterface
123
    {
124
        if (\method_exists($this->node, 'removeAttribute')) {
125
            $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...
126
        }
127
128
        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...
129
    }
130
131
    /**
132
     * Replace child node.
133
     *
134
     * @param string $string
135
     *
136
     * @return SimpleXmlDomInterface
137
     */
138 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...
139
    {
140
        if (!empty($string)) {
141
            $newDocument = new XmlDomParser($string);
142
143
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
144
            $tmpStr = $this->normalizeStringForComparision($string);
145
            if ($tmpDomString !== $tmpStr) {
146
                throw new \RuntimeException(
147
                    'Not valid HTML fragment!' . "\n" .
148
                    $tmpDomString . "\n" .
149
                    $tmpStr
150
                );
151
            }
152
        }
153
154
        if (\count($this->node->childNodes) > 0) {
155
            foreach ($this->node->childNodes as $node) {
156
                $this->node->removeChild($node);
157
            }
158
        }
159
160
        if (!empty($newDocument)) {
161
            $ownerDocument = $this->node->ownerDocument;
162
            if (
163
                $ownerDocument !== null
164
                &&
165
                $newDocument->getDocument()->documentElement !== null
166
            ) {
167
                $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
168
                /** @noinspection UnusedFunctionResultInspection */
169
                $this->node->appendChild($newNode);
170
            }
171
        }
172
173
        return $this;
174
    }
175
176
    /**
177
     * Replace this node.
178
     *
179
     * @param string $string
180
     *
181
     * @return SimpleXmlDomInterface
182
     */
183
    protected function replaceNodeWithString(string $string): SimpleXmlDomInterface
184
    {
185
        if (empty($string)) {
186
            $this->node->parentNode->removeChild($this->node);
187
188
            return $this;
189
        }
190
191
        $newDocument = new XmlDomParser($string);
192
193
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
194
        $tmpStr = $this->normalizeStringForComparision($string);
195 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...
196
            throw new \RuntimeException(
197
                'Not valid HTML fragment!' . "\n"
198
                . $tmpDomOuterTextString . "\n" .
199
                $tmpStr
200
            );
201
        }
202
203
        $ownerDocument = $this->node->ownerDocument;
204
        if (
205
            $ownerDocument === null
206
            ||
207
            $newDocument->getDocument()->documentElement === null
208
        ) {
209
            return $this;
210
        }
211
212
        $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
213
214
        $this->node->parentNode->replaceChild($newNode, $this->node);
215
        $this->node = $newNode;
216
217
        return $this;
218
    }
219
220
    /**
221
     * Replace this node with text
222
     *
223
     * @param string $string
224
     *
225
     * @return SimpleXmlDomInterface
226
     */
227 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...
228
    {
229
        if (empty($string)) {
230
            $this->node->parentNode->removeChild($this->node);
231
232
            return $this;
233
        }
234
235
        $ownerDocument = $this->node->ownerDocument;
236
        if ($ownerDocument !== null) {
237
            $newElement = $ownerDocument->createTextNode($string);
238
            $newNode = $ownerDocument->importNode($newElement, true);
239
            $this->node->parentNode->replaceChild($newNode, $this->node);
240
            $this->node = $newNode;
241
        }
242
243
        return $this;
244
    }
245
246
    /**
247
     * Set attribute value.
248
     *
249
     * @param string      $name       <p>The name of the html-attribute.</p>
250
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
251
     * @param bool        $strict     </p>
252
     *                                $value must be NULL, to remove the attribute,
253
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
254
     *                                </p>
255
     *
256
     * @return SimpleXmlDomInterface
257
     */
258 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...
259
    {
260
        if (
261
            ($strict && $value === null)
262
            ||
263
            (!$strict && empty($value))
264
        ) {
265
            /** @noinspection UnusedFunctionResultInspection */
266
            $this->removeAttribute($name);
267
        } elseif (\method_exists($this->node, 'setAttribute')) {
268
            /** @noinspection UnusedFunctionResultInspection */
269
            $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...
270
        }
271
272
        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...
273
    }
274
275
    /**
276
     * Get dom node's plain text.
277
     *
278
     * @return string
279
     */
280
    public function text(): string
281
    {
282
        return $this->getXmlDomParser()->fixHtmlOutput($this->node->textContent);
283
    }
284
285
    /**
286
     * Get dom node's outer html.
287
     *
288
     * @param bool $multiDecodeNewHtmlEntity
289
     *
290
     * @return string
291
     */
292
    public function xml(bool $multiDecodeNewHtmlEntity = false): string
293
    {
294
        return $this->getXmlDomParser()->xml($multiDecodeNewHtmlEntity, false);
295
    }
296
297
    /**
298
     * Change the name of a tag in a "DOMNode".
299
     *
300
     * @param \DOMNode $node
301
     * @param string   $name
302
     *
303
     * @return \DOMElement|false
304
     *                          <p>DOMElement a new instance of class DOMElement or false
305
     *                          if an error occured.</p>
306
     */
307 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...
308
    {
309
        $ownerDocument = $node->ownerDocument;
310
        if ($ownerDocument) {
311
            $newNode = $ownerDocument->createElement($name);
312
        } else {
313
            return false;
314
        }
315
316
        foreach ($node->childNodes as $child) {
317
            $child = $ownerDocument->importNode($child, true);
318
            /** @noinspection UnusedFunctionResultInspection */
319
            $newNode->appendChild($child);
320
        }
321
322
        foreach ($node->attributes as $attrName => $attrNode) {
323
            /** @noinspection UnusedFunctionResultInspection */
324
            $newNode->setAttribute($attrName, $attrNode);
325
        }
326
327
        /** @noinspection UnusedFunctionResultInspection */
328
        $newNode->ownerDocument->replaceChild($newNode, $node);
329
330
        return $newNode;
331
    }
332
333
    /**
334
     * Returns children of node.
335
     *
336
     * @param int $idx
337
     *
338
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface|null
339
     */
340 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...
341
    {
342
        $nodeList = $this->getIterator();
343
344
        if ($idx === -1) {
345
            return $nodeList;
346
        }
347
348
        return $nodeList[$idx] ?? null;
349
    }
350
351
    /**
352
     * Find nodes with a CSS selector.
353
     *
354
     * @param string $selector
355
     *
356
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
357
     */
358
    public function findMulti(string $selector): SimpleXmlDomNodeInterface
359
    {
360
        return $this->getXmlDomParser()->findMulti($selector);
361
    }
362
363
    /**
364
     * Find nodes with a CSS selector.
365
     *
366
     * @param string $selector
367
     *
368
     * @return false|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
369
     */
370
    public function findMultiOrFalse(string $selector)
371
    {
372
        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 372 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...
373
    }
374
375
    /**
376
     * Find one node with a CSS selector.
377
     *
378
     * @param string $selector
379
     *
380
     * @return SimpleXmlDomInterface
381
     */
382
    public function findOne(string $selector): SimpleXmlDomInterface
383
    {
384
        return $this->getXmlDomParser()->findOne($selector);
385
    }
386
387
    /**
388
     * Find one node with a CSS selector or false, if no element is found.
389
     *
390
     * @param string $selector
391
     *
392
     * @return false|SimpleXmlDomInterface
393
     */
394
    public function findOneOrFalse(string $selector)
395
    {
396
        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...
397
    }
398
399
    /**
400
     * Returns the first child of node.
401
     *
402
     * @return SimpleXmlDomInterface|null
403
     */
404 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...
405
    {
406
        /** @var \DOMNode|null $node */
407
        $node = $this->node->firstChild;
408
409
        if ($node === null) {
410
            return null;
411
        }
412
413
        return new static($node);
414
    }
415
416
    /**
417
     * Return elements by .class.
418
     *
419
     * @param string $class
420
     *
421
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
422
     */
423
    public function getElementByClass(string $class): SimpleXmlDomNodeInterface
424
    {
425
        return $this->findMulti(".${class}");
426
    }
427
428
    /**
429
     * Return element by #id.
430
     *
431
     * @param string $id
432
     *
433
     * @return SimpleXmlDomInterface
434
     */
435
    public function getElementById(string $id): SimpleXmlDomInterface
436
    {
437
        return $this->findOne("#${id}");
438
    }
439
440
    /**
441
     * Return element by tag name.
442
     *
443
     * @param string $name
444
     *
445
     * @return SimpleXmlDomInterface
446
     */
447 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...
448
    {
449
        if ($this->node instanceof \DOMElement) {
450
            $node = $this->node->getElementsByTagName($name)->item(0);
451
        } else {
452
            $node = null;
453
        }
454
455
        if ($node === null) {
456
            return new SimpleXmlDomBlank();
457
        }
458
459
        return new static($node);
460
    }
461
462
    /**
463
     * Returns elements by #id.
464
     *
465
     * @param string   $id
466
     * @param int|null $idx
467
     *
468
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
469
     */
470
    public function getElementsById(string $id, $idx = null)
471
    {
472
        return $this->find("#${id}", $idx);
473
    }
474
475
    /**
476
     * Returns elements by tag name.
477
     *
478
     * @param string   $name
479
     * @param int|null $idx
480
     *
481
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
482
     */
483 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...
484
    {
485
        if ($this->node instanceof \DOMElement) {
486
            $nodesList = $this->node->getElementsByTagName($name);
487
        } else {
488
            $nodesList = [];
489
        }
490
491
        $elements = new SimpleXmlDomNode();
492
493
        foreach ($nodesList as $node) {
494
            $elements[] = new static($node);
495
        }
496
497
        // return all elements
498
        if ($idx === null) {
499
            if (\count($elements) === 0) {
500
                return new SimpleXmlDomNodeBlank();
501
            }
502
503
            return $elements;
504
        }
505
506
        // handle negative values
507
        if ($idx < 0) {
508
            $idx = \count($elements) + $idx;
509
        }
510
511
        // return one element
512
        return $elements[$idx] ?? new SimpleXmlDomBlank();
513
    }
514
515
    /**
516
     * @return \DOMNode
517
     */
518 1
    public function getNode(): \DOMNode
519
    {
520 1
        return $this->node;
521
    }
522
523
    /**
524
     * Create a new "XmlDomParser"-object from the current context.
525
     *
526
     * @return XmlDomParser
527
     */
528
    public function getXmlDomParser(): XmlDomParser
529
    {
530
        return new XmlDomParser($this);
531
    }
532
533
    /**
534
     * Get dom node's inner html.
535
     *
536
     * @param bool $multiDecodeNewHtmlEntity
537
     *
538
     * @return string
539
     */
540
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false): string
541
    {
542
        return $this->getXmlDomParser()->innerHtml($multiDecodeNewHtmlEntity);
543
    }
544
545
    /**
546
     * Nodes can get partially destroyed in which they're still an
547
     * actual DOM node (such as \DOMElement) but almost their entire
548
     * body is gone, including the `nodeType` attribute.
549
     *
550
     * @return bool true if node has been destroyed
551
     */
552
    public function isRemoved(): bool
553
    {
554
        return !isset($this->node->nodeType);
555
    }
556
557
    /**
558
     * Returns the last child of node.
559
     *
560
     * @return SimpleXmlDomInterface|null
561
     */
562 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...
563
    {
564
        /** @var \DOMNode|null $node */
565
        $node = $this->node->lastChild;
566
567
        if ($node === null) {
568
            return null;
569
        }
570
571
        return new static($node);
572
    }
573
574
    /**
575
     * Returns the next sibling of node.
576
     *
577
     * @return SimpleXmlDomInterface|null
578
     */
579 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...
580
    {
581
        /** @var \DOMNode|null $node */
582
        $node = $this->node->nextSibling;
583
584
        if ($node === null) {
585
            return null;
586
        }
587
588
        return new static($node);
589
    }
590
591
    /**
592
     * Returns the parent of node.
593
     *
594
     * @return SimpleXmlDomInterface
595
     */
596
    public function parentNode(): SimpleXmlDomInterface
597
    {
598
        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...
599
    }
600
601
    /**
602
     * Returns the previous sibling of node.
603
     *
604
     * @return SimpleXmlDomInterface|null
605
     */
606 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...
607
    {
608
        /** @var \DOMNode|null $node */
609
        $node = $this->node->previousSibling;
610
611
        if ($node === null) {
612
            return null;
613
        }
614
615
        return new static($node);
616
    }
617
618
    /**
619
     * @param string|string[]|null $value <p>
620
     *                                    null === get the current input value
621
     *                                    text === set a new input value
622
     *                                    </p>
623
     *
624
     * @return string|string[]|null
625
     */
626 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...
627
    {
628
        if ($value === null) {
629
            if (
630
                $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...
631
                &&
632
                (
633
                    $this->getAttribute('type') === 'text'
634
                    ||
635
                    !$this->hasAttribute('type')
636
                )
637
            ) {
638
                return $this->getAttribute('value');
639
            }
640
641
            if (
642
                $this->hasAttribute('checked')
643
                &&
644
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
645
            ) {
646
                return $this->getAttribute('value');
647
            }
648
649
            if ($this->node->nodeName === 'select') {
650
                $valuesFromDom = [];
651
                $options = $this->getElementsByTagName('option');
652
                if ($options instanceof SimpleXmlDomNode) {
653
                    foreach ($options as $option) {
654
                        if ($this->hasAttribute('checked')) {
655
                            /** @noinspection UnnecessaryCastingInspection */
656
                            $valuesFromDom[] = (string) $option->getAttribute('value');
657
                        }
658
                    }
659
                }
660
661
                if (\count($valuesFromDom) === 0) {
662
                    return null;
663
                }
664
665
                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...
666
            }
667
668
            if ($this->node->nodeName === 'textarea') {
669
                return $this->node->nodeValue;
670
            }
671
        } else {
672
            /** @noinspection NestedPositiveIfStatementsInspection */
673
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
674
                if ($value === $this->getAttribute('value')) {
675
                    /** @noinspection UnusedFunctionResultInspection */
676
                    $this->setAttribute('checked', 'checked');
677
                } else {
678
                    /** @noinspection UnusedFunctionResultInspection */
679
                    $this->removeAttribute('checked');
680
                }
681
            } elseif ($this->node instanceof \DOMElement && $this->node->nodeName === 'select') {
682
                foreach ($this->node->getElementsByTagName('option') as $option) {
683
                    /** @var \DOMElement $option */
684
                    if ($value === $option->getAttribute('value')) {
685
                        /** @noinspection UnusedFunctionResultInspection */
686
                        $option->setAttribute('selected', 'selected');
687
                    } else {
688
                        $option->removeAttribute('selected');
689
                    }
690
                }
691
            } elseif ($this->node->nodeName === 'input' && \is_string($value)) {
692
                // Set value for input elements
693
                /** @noinspection UnusedFunctionResultInspection */
694
                $this->setAttribute('value', $value);
695
            } elseif ($this->node->nodeName === 'textarea' && \is_string($value)) {
696
                $this->node->nodeValue = $value;
697
            }
698
        }
699
700
        return null;
701
    }
702
703
    /**
704
     * Retrieve an external iterator.
705
     *
706
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
707
     *
708
     * @return SimpleXmlDomNode
709
     *                           <p>
710
     *                              An instance of an object implementing <b>Iterator</b> or
711
     *                              <b>Traversable</b>
712
     *                           </p>
713
     */
714 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...
715
    {
716
        $elements = new SimpleXmlDomNode();
717
        if ($this->node->hasChildNodes()) {
718
            foreach ($this->node->childNodes as $node) {
719
                $elements[] = new static($node);
720
            }
721
        }
722
723
        return $elements;
724
    }
725
726
    /**
727
     * Normalize the given input for comparision.
728
     *
729
     * @param string|XmlDomParser $input
730
     *
731
     * @return string
732
     */
733
    private function normalizeStringForComparision($input): string
734
    {
735
        if ($input instanceof XmlDomParser) {
736
            $string = $input->plaintext;
737
        } else {
738
            $string = (string) $input;
739
        }
740
741
        return
742
            \urlencode(
743
                \urldecode(
744
                    \trim(
745
                        \str_replace(
746
                            [
747
                                ' ',
748
                                "\n",
749
                                "\r",
750
                                '/>',
751
                            ],
752
                            [
753
                                '',
754
                                '',
755
                                '',
756
                                '>',
757
                            ],
758
                            \strtolower($string)
759
                        )
760
                    )
761
                )
762
            );
763
    }
764
}
765