DomPlugin   F
last analyzed

Complexity

Total Complexity 89

Size/Duplication

Total Lines 483
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 239
dl 0
loc 483
rs 2
c 1
b 0
f 0
wmc 89

11 Methods

Rating   Name   Duplication   Size   Complexity  
B getChildren() 0 36 9
F parseNode() 0 70 19
A getTypes() 0 3 1
B parseBegin() 0 17 11
C parseList() 0 65 16
A getTriggers() 0 3 1
A parseText() 0 7 3
C getKnownProperties() 0 52 12
A __construct() 0 6 1
A setParser() 0 6 1
C parseProperty() 0 33 15

How to fix   Complexity   

Complex Class

Complex classes like DomPlugin 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.

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 DomPlugin, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
11
 * this software and associated documentation files (the "Software"), to deal in
12
 * the Software without restriction, including without limitation the rights to
13
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
 * the Software, and to permit persons to whom the Software is furnished to do so,
15
 * subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
 */
27
28
namespace Kint\Parser;
29
30
use Dom\Attr;
0 ignored issues
show
Bug introduced by
The type Dom\Attr was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
use Dom\CharacterData;
0 ignored issues
show
Bug introduced by
The type Dom\CharacterData was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
32
use Dom\Document;
0 ignored issues
show
Bug introduced by
The type Dom\Document was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
use Dom\DocumentType;
0 ignored issues
show
Bug introduced by
The type Dom\DocumentType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
use Dom\Element;
0 ignored issues
show
Bug introduced by
The type Dom\Element was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
use Dom\HTMLElement;
0 ignored issues
show
Bug introduced by
The type Dom\HTMLElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
use Dom\NamedNodeMap;
0 ignored issues
show
Bug introduced by
The type Dom\NamedNodeMap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
37
use Dom\Node;
0 ignored issues
show
Bug introduced by
The type Dom\Node was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
use Dom\NodeList;
0 ignored issues
show
Bug introduced by
The type Dom\NodeList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
use DOMAttr;
40
use DOMCharacterData;
41
use DOMDocumentType;
42
use DOMElement;
43
use DOMNamedNodeMap;
44
use DOMNode;
45
use DOMNodeList;
46
use Kint\Value\AbstractValue;
47
use Kint\Value\Context\BaseContext;
48
use Kint\Value\Context\ClassDeclaredContext;
49
use Kint\Value\Context\ContextInterface;
50
use Kint\Value\Context\PropertyContext;
51
use Kint\Value\DomNodeListValue;
52
use Kint\Value\DomNodeValue;
53
use Kint\Value\FixedWidthValue;
54
use Kint\Value\InstanceValue;
55
use Kint\Value\Representation\ContainerRepresentation;
56
use Kint\Value\StringValue;
57
use LogicException;
58
59
class DomPlugin extends AbstractPlugin implements PluginBeginInterface
60
{
61
    /**
62
     * Reflection doesn't work below 8.1, also it won't show readonly status.
63
     *
64
     * In order to ensure this is stable enough we're only going to provide
65
     * properties for element and node. If subclasses like attr or document
66
     * have their own fields then tough shit we're not showing them.
67
     *
68
     * @psalm-var non-empty-array<string, bool> Property names to readable status
69
     */
70
    public const NODE_PROPS = [
71
        'nodeType' => true,
72
        'nodeName' => true,
73
        'baseURI' => true,
74
        'isConnected' => true,
75
        'ownerDocument' => true,
76
        'parentNode' => true,
77
        'parentElement' => true,
78
        'childNodes' => true,
79
        'firstChild' => true,
80
        'lastChild' => true,
81
        'previousSibling' => true,
82
        'nextSibling' => true,
83
        'nodeValue' => true,
84
        'textContent' => false,
85
    ];
86
87
    /**
88
     * @psalm-var non-empty-array<string, bool> Property names to readable status
89
     */
90
    public const ELEMENT_PROPS = [
91
        'namespaceURI' => true,
92
        'prefix' => true,
93
        'localName' => true,
94
        'tagName' => true,
95
        'id' => false,
96
        'className' => false,
97
        'classList' => true,
98
        'attributes' => true,
99
        'firstElementChild' => true,
100
        'lastElementChild' => true,
101
        'childElementCount' => true,
102
        'previousElementSibling' => true,
103
        'nextElementSibling' => true,
104
        'innerHTML' => false,
105
        'outerHTML' => false,
106
        'substitutedNodeValue' => false,
107
    ];
108
109
    public const DOM_NS_VERSIONS = [
110
        'outerHTML' => KINT_PHP85,
111
    ];
112
113
    /**
114
     * @psalm-var non-empty-array<string, bool> Property names to readable status
115
     */
116
    public const DOMNODE_PROPS = [
117
        'nodeName' => true,
118
        'nodeValue' => false,
119
        'nodeType' => true,
120
        'parentNode' => true,
121
        'parentElement' => true,
122
        'childNodes' => true,
123
        'firstChild' => true,
124
        'lastChild' => true,
125
        'previousSibling' => true,
126
        'nextSibling' => true,
127
        'attributes' => true,
128
        'isConnected' => true,
129
        'ownerDocument' => true,
130
        'namespaceURI' => true,
131
        'prefix' => false,
132
        'localName' => true,
133
        'baseURI' => true,
134
        'textContent' => false,
135
    ];
136
137
    /**
138
     * @psalm-var non-empty-array<string, bool> Property names to readable status
139
     */
140
    public const DOMELEMENT_PROPS = [
141
        'tagName' => true,
142
        'className' => false,
143
        'id' => false,
144
        'schemaTypeInfo' => true,
145
        'firstElementChild' => true,
146
        'lastElementChild' => true,
147
        'childElementCount' => true,
148
        'previousElementSibling' => true,
149
        'nextElementSibling' => true,
150
    ];
151
152
    public const DOM_VERSIONS = [
153
        'parentElement' => KINT_PHP83,
154
        'isConnected' => KINT_PHP83,
155
        'className' => KINT_PHP83,
156
        'id' => KINT_PHP83,
157
        'firstElementChild' => KINT_PHP80,
158
        'lastElementChild' => KINT_PHP80,
159
        'childElementCount' => KINT_PHP80,
160
        'previousElementSibling' => KINT_PHP80,
161
        'nextElementSibling' => KINT_PHP80,
162
    ];
163
164
    /**
165
     * List of properties to skip parsing.
166
     *
167
     * The properties of a Dom\Node can do a *lot* of damage to debuggers. The
168
     * Dom\Node contains not one, not two, but 13 different ways to recurse into itself:
169
     * * parentNode
170
     * * firstChild
171
     * * lastChild
172
     * * previousSibling
173
     * * nextSibling
174
     * * parentElement
175
     * * firstElementChild
176
     * * lastElementChild
177
     * * previousElementSibling
178
     * * nextElementSibling
179
     * * childNodes
180
     * * attributes
181
     * * ownerDocument
182
     *
183
     * All of this combined: the tiny SVGs used as the caret in Kint were already
184
     * enough to make parsing and rendering take over a second, and send memory
185
     * usage over 128 megs, back in the old DOM API. So we blacklist every field
186
     * we don't strictly need and hope that that's good enough.
187
     *
188
     * In retrospect -- this is probably why print_r does the same
189
     *
190
     * @psalm-var array<string, true>
191
     */
192
    public static array $blacklist = [
193
        'parentNode' => true,
194
        'firstChild' => true,
195
        'lastChild' => true,
196
        'previousSibling' => true,
197
        'nextSibling' => true,
198
        'firstElementChild' => true,
199
        'lastElementChild' => true,
200
        'parentElement' => true,
201
        'previousElementSibling' => true,
202
        'nextElementSibling' => true,
203
        'ownerDocument' => true,
204
    ];
205
206
    /**
207
     * Show all properties and methods.
208
     */
209
    public static bool $verbose = false;
210
211
    protected ClassMethodsPlugin $methods_plugin;
212
    protected ClassStaticsPlugin $statics_plugin;
213
214
    public function __construct(Parser $parser)
215
    {
216
        parent::__construct($parser);
217
218
        $this->methods_plugin = new ClassMethodsPlugin($parser);
219
        $this->statics_plugin = new ClassStaticsPlugin($parser);
220
    }
221
222
    public function setParser(Parser $p): void
223
    {
224
        parent::setParser($p);
225
226
        $this->methods_plugin->setParser($p);
227
        $this->statics_plugin->setParser($p);
228
    }
229
230
    public function getTypes(): array
231
    {
232
        return ['object'];
233
    }
234
235
    public function getTriggers(): int
236
    {
237
        return Parser::TRIGGER_BEGIN;
238
    }
239
240
    public function parseBegin(&$var, ContextInterface $c): ?AbstractValue
241
    {
242
        // Attributes and chardata (Which is parent of comments and text
243
        // nodes) don't need children or attributes of their own
244
        if ($var instanceof Attr || $var instanceof CharacterData || $var instanceof DOMAttr || $var instanceof DOMCharacterData) {
245
            return $this->parseText($var, $c);
246
        }
247
248
        if ($var instanceof NamedNodeMap || $var instanceof NodeList || $var instanceof DOMNamedNodeMap || $var instanceof DOMNodeList) {
249
            return $this->parseList($var, $c);
250
        }
251
252
        if ($var instanceof Node || $var instanceof DOMNode) {
253
            return $this->parseNode($var, $c);
254
        }
255
256
        return null;
257
    }
258
259
    /** @psalm-param Node|DOMNode $var */
260
    private function parseProperty(object $var, string $prop, ContextInterface $c): AbstractValue
261
    {
262
        if (!isset($var->{$prop})) {
263
            return new FixedWidthValue($c, null);
264
        }
265
266
        $parser = $this->getParser();
267
        $value = $var->{$prop};
268
269
        if (\is_scalar($value)) {
270
            return $parser->parse($value, $c);
271
        }
272
273
        if (isset(self::$blacklist[$prop])) {
274
            $b = new InstanceValue($c, \get_class($value), \spl_object_hash($value), \spl_object_id($value));
275
            $b->flags |= AbstractValue::FLAG_GENERATED | AbstractValue::FLAG_BLACKLIST;
276
277
            return $b;
278
        }
279
280
        // Everything we can handle in parseBegin
281
        if ($value instanceof Attr || $value instanceof CharacterData || $value instanceof DOMAttr || $value instanceof DOMCharacterData || $value instanceof NamedNodeMap || $value instanceof NodeList || $value instanceof DOMNamedNodeMap || $value instanceof DOMNodeList || $value instanceof Node || $value instanceof DOMNode) {
282
            $out = $this->parseBegin($value, $c);
283
        }
284
285
        if (!isset($out)) {
286
            // Shouldn't ever happen
287
            $out = $parser->parse($value, $c); // @codeCoverageIgnore
288
        }
289
290
        $out->flags |= AbstractValue::FLAG_GENERATED;
291
292
        return $out;
293
    }
294
295
    /** @psalm-param Attr|CharacterData|DOMAttr|DOMCharacterData $var */
296
    private function parseText(object $var, ContextInterface $c): AbstractValue
297
    {
298
        if ($c instanceof BaseContext && null !== $c->access_path) {
299
            $c->access_path .= '->nodeValue';
300
        }
301
302
        return $this->parseProperty($var, 'nodeValue', $c);
303
    }
304
305
    /** @psalm-param NamedNodeMap|NodeList|DOMNamedNodeMap|DOMNodeList $var */
306
    private function parseList(object $var, ContextInterface $c): InstanceValue
307
    {
308
        if ($var instanceof NodeList || $var instanceof DOMNodeList) {
309
            $v = new DomNodeListValue($c, $var);
310
        } else {
311
            $v = new InstanceValue($c, \get_class($var), \spl_object_hash($var), \spl_object_id($var));
312
        }
313
314
        $parser = $this->getParser();
315
        $pdepth = $parser->getDepthLimit();
316
317
        // Depth limit
318
        // Use empty iterator representation since we need it to point out depth limits
319
        if (($var instanceof NodeList || $var instanceof DOMNodeList) && $pdepth && $c->getDepth() >= $pdepth) {
320
            $v->flags |= AbstractValue::FLAG_DEPTH_LIMIT;
321
322
            return $v;
323
        }
324
325
        if (self::$verbose) {
326
            $v = $this->methods_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
327
            $v = $this->statics_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
328
        }
329
330
        if (0 === $var->length) {
331
            $v->setChildren([]);
332
333
            return $v;
334
        }
335
336
        $cdepth = $c->getDepth();
337
        $ap = $c->getAccessPath();
338
        $contents = [];
339
340
        foreach ($var as $key => $item) {
341
            $base_obj = new BaseContext($item->nodeName);
342
            $base_obj->depth = $cdepth + 1;
343
344
            if ($var instanceof NamedNodeMap || $var instanceof DOMNamedNodeMap) {
345
                if (null !== $ap) {
346
                    $base_obj->access_path = $ap.'['.\var_export($item->nodeName, true).']';
347
                }
348
            } else { // NodeList
349
                if (null !== $ap) {
350
                    $base_obj->access_path = $ap.'['.\var_export($key, true).']';
351
                }
352
            }
353
354
            if ($item instanceof HTMLElement) {
355
                $base_obj->name = $item->localName;
356
            }
357
358
            $item = $parser->parse($item, $base_obj);
359
            $item->flags |= AbstractValue::FLAG_GENERATED;
360
361
            $contents[] = $item;
362
        }
363
364
        $v->setChildren($contents);
365
366
        if ($contents) {
367
            $v->addRepresentation(new ContainerRepresentation('Iterator', $contents), 0);
368
        }
369
370
        return $v;
371
    }
372
373
    /** @psalm-param Node|DOMNode $var */
374
    private function parseNode(object $var, ContextInterface $c): DomNodeValue
375
    {
376
        $class = \get_class($var);
377
        $pdepth = $this->getParser()->getDepthLimit();
378
379
        if ($pdepth && $c->getDepth() >= $pdepth) {
380
            $v = new DomNodeValue($c, $var);
381
            $v->flags |= AbstractValue::FLAG_DEPTH_LIMIT;
382
383
            return $v;
384
        }
385
386
        if (($var instanceof DocumentType || $var instanceof DOMDocumentType) && $c instanceof BaseContext && $c->name === $var->nodeName) {
387
            $c->name = '!DOCTYPE '.$c->name;
388
        }
389
390
        $cdepth = $c->getDepth();
391
        $ap = $c->getAccessPath();
392
393
        $properties = [];
394
        $children = [];
395
        $attributes = [];
396
397
        foreach (self::getKnownProperties($var) as $prop => $readonly) {
398
            $prop_c = new PropertyContext($prop, $class, ClassDeclaredContext::ACCESS_PUBLIC);
399
            $prop_c->depth = $cdepth + 1;
400
            $prop_c->readonly = KINT_PHP81 && $readonly;
401
402
            if (null !== $ap) {
403
                $prop_c->access_path = $ap.'->'.$prop;
404
            }
405
406
            $properties[] = $prop_obj = $this->parseProperty($var, $prop, $prop_c);
407
408
            if ('childNodes' === $prop) {
409
                if (!$prop_obj instanceof DomNodeListValue) {
410
                    throw new LogicException('childNodes property parsed incorrectly'); // @codeCoverageIgnore
411
                }
412
                $children = self::getChildren($prop_obj);
413
            } elseif ('attributes' === $prop) {
414
                $attributes = $prop_obj->getRepresentation('iterator');
415
                $attributes = $attributes instanceof ContainerRepresentation ? $attributes->getContents() : [];
416
            } elseif ('classList' === $prop) {
417
                if ($iter = $prop_obj->getRepresentation('iterator')) {
418
                    $prop_obj->removeRepresentation($iter);
419
                    $prop_obj->addRepresentation($iter, 0);
420
                }
421
            }
422
        }
423
424
        $v = new DomNodeValue($c, $var);
425
        // If we're in text mode, we can see children through the childNodes property
426
        $v->setChildren($properties);
427
428
        if ($children) {
429
            $v->addRepresentation(new ContainerRepresentation('Children', $children, null, true));
430
        }
431
432
        if ($attributes) {
433
            $v->addRepresentation(new ContainerRepresentation('Attributes', $attributes));
434
        }
435
436
        if (self::$verbose) {
437
            $v->addRepresentation(new ContainerRepresentation('Properties', $properties));
438
439
            $v = $this->methods_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
440
            $v = $this->statics_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
441
        }
442
443
        return $v;
444
    }
445
446
    /**
447
     * @psalm-param Node|DOMNode $var
448
     *
449
     * @psalm-return non-empty-array<string, bool>
450
     */
451
    public static function getKnownProperties(object $var): array
452
    {
453
        if ($var instanceof Node) {
454
            $known_properties = self::NODE_PROPS;
455
            if ($var instanceof Element) {
456
                $known_properties += self::ELEMENT_PROPS;
457
            }
458
459
            if ($var instanceof Document) {
460
                $known_properties['textContent'] = true;
461
            }
462
463
            if ($var instanceof Attr || $var instanceof CharacterData) {
464
                $known_properties['nodeValue'] = false;
465
            }
466
467
            foreach (self::DOM_NS_VERSIONS as $key => $val) {
468
                /**
469
                 * @psalm-var bool $val
470
                 * Psalm bug #4509
471
                 */
472
                if (false === $val) {
473
                    unset($known_properties[$key]); // @codeCoverageIgnore
474
                }
475
            }
476
        } else {
477
            $known_properties = self::DOMNODE_PROPS;
478
            if ($var instanceof DOMElement) {
479
                $known_properties += self::DOMELEMENT_PROPS;
480
            }
481
482
            foreach (self::DOM_VERSIONS as $key => $val) {
483
                /**
484
                 * @psalm-var bool $val
485
                 * Psalm bug #4509
486
                 */
487
                if (false === $val) {
488
                    unset($known_properties[$key]); // @codeCoverageIgnore
489
                }
490
            }
491
        }
492
493
        /** @psalm-var non-empty-array $known_properties */
494
        if (!self::$verbose) {
495
            $known_properties = \array_intersect_key($known_properties, [
496
                'nodeValue' => null,
497
                'childNodes' => null,
498
                'attributes' => null,
499
            ]);
500
        }
501
502
        return $known_properties;
503
    }
504
505
    /** @psalm-return list<AbstractValue> */
506
    private static function getChildren(DomNodeListValue $property): array
507
    {
508
        if (0 === $property->getLength()) {
509
            return [];
510
        }
511
512
        if ($property->flags & AbstractValue::FLAG_DEPTH_LIMIT) {
513
            return [$property];
514
        }
515
516
        $list_items = $property->getChildren();
517
518
        if (null === $list_items) {
519
            // This is here for psalm but all DomNodeListValue should
520
            // either be depth_limit or have array children
521
            return []; // @codeCoverageIgnore
522
        }
523
524
        $children = [];
525
526
        foreach ($list_items as $node) {
527
            // Remove text nodes if theyre empty
528
            if ($node instanceof StringValue && '#text' === $node->getContext()->getName()) {
529
                /**
530
                 * @psalm-suppress InvalidArgument
531
                 * Psalm bug #11055
532
                 */
533
                if (\ctype_space($node->getValue()) || '' === $node->getValue()) {
534
                    continue;
535
                }
536
            }
537
538
            $children[] = $node;
539
        }
540
541
        return $children;
542
    }
543
}
544