Passed
Push — master ( ba645d...9eba8c )
by Michael
07:09 queued 43s
created

DOMDocumentPlugin::parseNode()   F

Complexity

Conditions 21
Paths 400

Size

Total Lines 103
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 21
eloc 62
c 1
b 0
f 0
nc 400
nop 2
dl 0
loc 103
rs 0.8333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9
 * this software and associated documentation files (the "Software"), to deal in
10
 * the Software without restriction, including without limitation the rights to
11
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12
 * the Software, and to permit persons to whom the Software is furnished to do so,
13
 * subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in all
16
 * copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
 */
25
26
namespace Kint\Parser;
27
28
use DOMNamedNodeMap;
29
use DOMNode;
30
use DOMNodeList;
31
use Kint\Object\BasicObject;
32
use Kint\Object\InstanceObject;
33
use Kint\Object\Representation\Representation;
34
35
/**
36
 * The DOMDocument parser plugin is particularly useful as it is both the only
37
 * way to see inside the DOMNode without print_r, and the only way to see mixed
38
 * text and node inside XML (SimpleXMLElement will strip out the text).
39
 */
40
class DOMDocumentPlugin extends Plugin
41
{
42
    /**
43
     * List of properties to skip parsing.
44
     *
45
     * The properties of a DOMNode can do a *lot* of damage to debuggers. The
46
     * DOMNode contains not one, not two, not three, not four, not 5, not 6,
47
     * not 7 but 8 different ways to recurse into itself:
48
     * * firstChild
49
     * * lastChild
50
     * * previousSibling
51
     * * nextSibling
52
     * * ownerDocument
53
     * * parentNode
54
     * * childNodes
55
     * * attributes
56
     *
57
     * All of this combined: the tiny SVGs used as the caret in Kint are already
58
     * enough to make parsing and rendering take over a second, and send memory
59
     * usage over 128 megs. So we blacklist every field we don't strictly need
60
     * and hope that that's good enough.
61
     *
62
     * In retrospect - this is probably why print_r does the same
63
     *
64
     * @var array
65
     */
66
    public static $blacklist = array(
67
        'parentNode' => 'DOMNode',
68
        'firstChild' => 'DOMNode',
69
        'lastChild' => 'DOMNode',
70
        'previousSibling' => 'DOMNode',
71
        'nextSibling' => 'DOMNode',
72
        'ownerDocument' => 'DOMDocument',
73
    );
74
75
    /**
76
     * Show all properties and methods.
77
     *
78
     * @var bool
79
     */
80
    public static $verbose = false;
81
82
    public function getTypes()
83
    {
84
        return array('object');
85
    }
86
87
    public function getTriggers()
88
    {
89
        return Parser::TRIGGER_SUCCESS;
90
    }
91
92
    public function parse(&$var, BasicObject &$o, $trigger)
93
    {
94
        if (!$o instanceof InstanceObject) {
95
            return;
96
        }
97
98
        if ($var instanceof DOMNamedNodeMap || $var instanceof DOMNodeList) {
99
            return $this->parseList($var, $o, $trigger);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseList($var, $o, $trigger) targeting Kint\Parser\DOMDocumentPlugin::parseList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
100
        }
101
102
        if ($var instanceof DOMNode) {
103
            return $this->parseNode($var, $o);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->parseNode($var, $o) targeting Kint\Parser\DOMDocumentPlugin::parseNode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
104
        }
105
    }
106
107
    protected function parseList(&$var, InstanceObject &$o, $trigger)
108
    {
109
        // Recursion should never happen, should always be stopped at the parent
110
        // DOMNode.  Depth limit on the other hand we're going to skip since
111
        // that would show an empty iterator and rather useless. Let the depth
112
        // limit hit the children (DOMNodeList only has DOMNode as children)
113
        if ($trigger & Parser::TRIGGER_RECURSION) {
114
            return;
115
        }
116
117
        $o->size = $var->length;
118
        if (0 === $o->size) {
119
            $o->replaceRepresentation(new Representation('Iterator'));
120
            $o->size = null;
121
122
            return;
123
        }
124
125
        // Depth limit
126
        // Make empty iterator representation since we need it in DOMNode to point out depth limits
127
        if ($this->parser->getDepthLimit() && $o->depth + 1 >= $this->parser->getDepthLimit()) {
128
            $b = new BasicObject();
129
            $b->name = $o->classname.' Iterator Contents';
130
            $b->access_path = 'iterator_to_array('.$o->access_path.')';
131
            $b->depth = $o->depth + 1;
132
            $b->hints[] = 'depth_limit';
133
134
            $r = new Representation('Iterator');
135
            $r->contents = array($b);
136
            $o->replaceRepresentation($r, 0);
137
138
            return;
139
        }
140
141
        $data = \iterator_to_array($var);
142
143
        $r = new Representation('Iterator');
144
        $o->replaceRepresentation($r, 0);
145
146
        foreach ($data as $key => $item) {
147
            $base_obj = new BasicObject();
148
            $base_obj->depth = $o->depth + 1;
149
            $base_obj->name = $item->nodeName;
150
151
            if ($o->access_path) {
152
                if ($var instanceof DOMNamedNodeMap) {
153
                    $base_obj->access_path = $o->access_path.'->getNamedItem('.\var_export($key, true).')';
154
                } elseif ($var instanceof DOMNodeList) {
155
                    $base_obj->access_path = $o->access_path.'->item('.\var_export($key, true).')';
156
                } else {
157
                    $base_obj->access_path = 'iterator_to_array('.$o->access_path.')';
158
                }
159
            }
160
161
            $r->contents[] = $this->parser->parse($item, $base_obj);
162
        }
163
    }
164
165
    protected function parseNode(&$var, InstanceObject &$o)
166
    {
167
        // Fill the properties
168
        // They can't be enumerated through reflection or casting,
169
        // so we have to trust the docs and try them one at a time
170
        $known_properties = array(
171
            'nodeValue',
172
            'childNodes',
173
            'attributes',
174
        );
175
176
        if (self::$verbose) {
177
            $known_properties = array(
178
                'nodeName',
179
                'nodeValue',
180
                'nodeType',
181
                'parentNode',
182
                'childNodes',
183
                'firstChild',
184
                'lastChild',
185
                'previousSibling',
186
                'nextSibling',
187
                'attributes',
188
                'ownerDocument',
189
                'namespaceURI',
190
                'prefix',
191
                'localName',
192
                'baseURI',
193
                'textContent',
194
            );
195
        }
196
197
        $childNodes = array();
198
        $attributes = array();
199
200
        $rep = $o->value;
201
202
        foreach ($known_properties as $prop) {
203
            $prop_obj = $this->parseProperty($o, $prop, $var);
204
            $rep->contents[] = $prop_obj;
205
206
            if ('childNodes' === $prop) {
207
                $childNodes = $prop_obj->getRepresentation('iterator');
208
            } elseif ('attributes' === $prop) {
209
                $attributes = $prop_obj->getRepresentation('iterator');
210
            }
211
        }
212
213
        if (!self::$verbose) {
214
            $o->removeRepresentation('methods');
215
            $o->removeRepresentation('properties');
216
        }
217
218
        // Attributes and comments and text nodes don't
219
        // need children or attributes of their own
220
        if (\in_array($o->classname, array('DOMAttr', 'DOMText', 'DOMComment'), true)) {
221
            return;
222
        }
223
224
        // Set the attributes
225
        if ($attributes) {
226
            $a = new Representation('Attributes');
227
            foreach ($attributes->contents as $attribute) {
228
                $a->contents[] = self::textualNodeToString($attribute);
229
            }
230
            $o->addRepresentation($a, 0);
231
        }
232
233
        // Set the children
234
        if ($childNodes) {
235
            $c = new Representation('Children');
236
237
            if (1 === \count($childNodes->contents) && ($node = \reset($childNodes->contents)) && \in_array('depth_limit', $node->hints, true)) {
238
                $n = new InstanceObject();
239
                $n->transplant($node);
240
                $n->name = 'childNodes';
241
                $n->classname = 'DOMNodeList';
242
                $c->contents = array($n);
243
            } else {
244
                foreach ($childNodes->contents as $index => $node) {
245
                    // Shortcircuit text nodes to plain strings
246
                    if ('DOMText' === $node->classname || 'DOMComment' === $node->classname) {
247
                        $node = self::textualNodeToString($node);
248
249
                        // And remove them if they're empty
250
                        if (\ctype_space($node->value->contents) || '' === $node->value->contents) {
251
                            continue;
252
                        }
253
                    }
254
255
                    $c->contents[] = $node;
256
                }
257
            }
258
259
            $o->addRepresentation($c, 0);
260
        }
261
262
        if (isset($c) && \count($c->contents)) {
263
            $o->size = \count($c->contents);
264
        }
265
266
        if (!$o->size) {
267
            $o->size = null;
268
        }
269
    }
270
271
    protected function parseProperty(InstanceObject $o, $prop, &$var)
272
    {
273
        // Duplicating (And slightly optimizing) the Parser::parseObject() code here
274
        $base_obj = new BasicObject();
275
        $base_obj->depth = $o->depth + 1;
276
        $base_obj->owner_class = $o->classname;
277
        $base_obj->name = $prop;
278
        $base_obj->operator = BasicObject::OPERATOR_OBJECT;
279
        $base_obj->access = BasicObject::ACCESS_PUBLIC;
280
281
        if (null !== $o->access_path) {
282
            $base_obj->access_path = $o->access_path;
283
284
            if (\preg_match('/^[A-Za-z0-9_]+$/', $base_obj->name)) {
285
                $base_obj->access_path .= '->'.$base_obj->name;
286
            } else {
287
                $base_obj->access_path .= '->{'.\var_export($base_obj->name, true).'}';
288
            }
289
        }
290
291
        if (!isset($var->{$prop})) {
292
            $base_obj->type = 'null';
293
        } elseif (isset(self::$blacklist[$prop])) {
294
            $b = new InstanceObject();
295
            $b->transplant($base_obj);
296
            $base_obj = $b;
297
298
            $base_obj->hints[] = 'blacklist';
299
            $base_obj->classname = self::$blacklist[$prop];
300
        } elseif ('attributes' === $prop) {
301
            $base_obj = $this->parser->parseDeep($var->{$prop}, $base_obj);
302
        } else {
303
            $base_obj = $this->parser->parse($var->{$prop}, $base_obj);
304
        }
305
306
        return $base_obj;
307
    }
308
309
    protected static function textualNodeToString(InstanceObject $o)
310
    {
311
        if (empty($o->value) || empty($o->value->contents) || empty($o->classname)) {
312
            return;
313
        }
314
315
        if (!\in_array($o->classname, array('DOMText', 'DOMAttr', 'DOMComment'), true)) {
316
            return;
317
        }
318
319
        foreach ($o->value->contents as $property) {
320
            if ('nodeValue' === $property->name) {
321
                $ret = clone $property;
322
                $ret->name = $o->name;
323
324
                return $ret;
325
            }
326
        }
327
    }
328
}
329