|
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; |
|
|
|
|
|
|
31
|
|
|
use Dom\CharacterData; |
|
|
|
|
|
|
32
|
|
|
use Dom\Document; |
|
|
|
|
|
|
33
|
|
|
use Dom\DocumentType; |
|
|
|
|
|
|
34
|
|
|
use Dom\Element; |
|
|
|
|
|
|
35
|
|
|
use Dom\HTMLElement; |
|
|
|
|
|
|
36
|
|
|
use Dom\NamedNodeMap; |
|
|
|
|
|
|
37
|
|
|
use Dom\Node; |
|
|
|
|
|
|
38
|
|
|
use Dom\NodeList; |
|
|
|
|
|
|
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
|
|
|
|
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths