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