Completed
Push — master ( 236b50...7a7214 )
by Lars
02:07
created

SimpleXmlDom   F

Complexity

Total Complexity 102

Size/Duplication

Total Lines 764
Duplicated Lines 39.53 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 2.19%

Importance

Changes 0
Metric Value
wmc 102
lcom 1
cbo 5
dl 302
loc 764
ccs 5
cts 228
cp 0.0219
rs 1.836
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A __call() 0 10 2
A find() 0 4 1
A getAllAttributes() 13 13 3
A hasAttributes() 0 3 1
A getAttribute() 0 10 2
A hasAttribute() 0 8 2
A innerXml() 0 4 1
A removeAttribute() 0 8 2
B replaceChildWithString() 37 37 8
A replaceNodeWithString() 7 36 5
A replaceTextWithString() 18 18 3
A setAttribute() 16 16 6
A text() 0 4 1
A xml() 0 4 1
A changeElementName() 25 25 4
A childNodes() 10 10 2
A findMulti() 0 4 1
A findMultiOrFalse() 0 4 1
A findOne() 0 4 1
A findOneOrFalse() 0 4 1
A firstChild() 11 11 2
A getElementByClass() 0 4 1
A getElementById() 0 4 1
A getElementByTagName() 14 14 3
A getElementsById() 0 4 1
B getElementsByTagName() 31 31 6
A getNode() 0 4 1
A getXmlDomParser() 0 4 1
A innerHtml() 0 4 1
A isRemoved() 0 4 1
A lastChild() 11 11 2
A nextSibling() 11 11 2
A parentNode() 0 4 1
A previousSibling() 11 11 2
D val() 76 76 23
A getIterator() 11 11 3
A normalizeStringForComparision() 0 31 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SimpleXmlDom often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SimpleXmlDom, and based on these observations, apply Extract Interface, too.

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