Passed
Branch master (038e9c)
by Michael
12:17 queued 06:21
created

Kint_Parser::parseObject()   F

Complexity

Conditions 21
Paths 1572

Size

Total Lines 130
Code Lines 75

Duplication

Lines 31
Ratio 23.85 %

Importance

Changes 0
Metric Value
cc 21
eloc 75
nc 1572
nop 2
dl 31
loc 130
rs 2
c 0
b 0
f 0

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
class Kint_Parser
4
{
5
    public $caller_class;
6
    public $max_depth;
7
8
    private $marker;
9
    private $object_hashes = array();
10
    private $parse_break = false;
11
    private $plugins = array();
12
13
    /**
14
     * Plugin triggers.
15
     *
16
     * These are constants indicating trigger points for plugins
17
     *
18
     * BEGIN: Before normal parsing
19
     * SUCCESS: After successful parsing
20
     * RECURSION: After parsing cancelled by recursion
21
     * DEPTH_LIMIT: After parsing cancelled by depth limit
22
     * COMPLETE: SUCCESS | RECURSION | DEPTH_LIMIT
23
     *
24
     * While a plugin's getTriggers may return any of these
25
     */
26
    const TRIGGER_NONE = 0;
27
    const TRIGGER_BEGIN = 1;
28
    const TRIGGER_SUCCESS = 2;
29
    const TRIGGER_RECURSION = 4;
30
    const TRIGGER_DEPTH_LIMIT = 8;
31
    const TRIGGER_COMPLETE = 14;
32
33
    public function __construct($max_depth = false, $c = null)
34
    {
35
        $this->marker = uniqid("kint\0", true);
36
        $this->caller_class = $c;
37
        $this->max_depth = $max_depth;
38
    }
39
40
    public function parse(&$var, Kint_Object $o)
41
    {
42
        $o->type = strtolower(gettype($var));
43
44
        if (!$this->applyPlugins($var, $o, self::TRIGGER_BEGIN)) {
45
            return $o;
46
        }
47
48
        switch ($o->type) {
49
            case 'array':
50
                return $this->parseArray($var, $o);
51
            case 'boolean':
52
            case 'double':
53
            case 'integer':
54
            case 'null':
55
                return $this->parseGeneric($var, $o);
56
            case 'object':
57
                return $this->parseObject($var, $o);
58
            case 'resource':
59
                return $this->parseResource($var, $o);
60
            case 'string':
61
                return $this->parseString($var, $o);
62
            default:
63
                return $this->parseUnknown($var, $o);
64
        }
65
    }
66
67
    private function parseGeneric(&$var, Kint_Object $o)
68
    {
69
        $rep = new Kint_Object_Representation('Contents');
70
        $rep->contents = $var;
71
        $rep->implicit_label = true;
72
        $o->addRepresentation($rep);
73
74
        $this->applyPlugins($var, $o, self::TRIGGER_SUCCESS);
75
76
        return $o;
77
    }
78
79
    private function parseString(&$var, Kint_Object $o)
80
    {
81
        $string = $o->transplant(new Kint_Object_Blob());
82
        $string->encoding = Kint_Object_Blob::detectEncoding($var);
83
        $string->size = Kint_Object_Blob::strlen($var, $string->encoding);
84
85
        $rep = new Kint_Object_Representation('Contents');
86
        $rep->contents = $var;
87
        $rep->implicit_label = true;
88
89
        $string->addRepresentation($rep);
90
91
        $this->applyPlugins($var, $string, self::TRIGGER_SUCCESS);
92
93
        return $string;
94
    }
95
96
    private function parseArray(array &$var, Kint_Object $o)
97
    {
98
        $array = $o->transplant(new Kint_Object());
99
        $array->size = count($var);
100
101 View Code Duplication
        if (isset($var[$this->marker])) {
102
            --$array->size;
103
            $array->hints[] = 'recursion';
104
105
            $this->applyPlugins($var, $array, self::TRIGGER_RECURSION);
106
107
            return $array;
108
        }
109
110
        $rep = new Kint_Object_Representation('Contents');
111
        $rep->implicit_label = true;
112
        $array->addRepresentation($rep);
113
114
        if ($array->size) {
115 View Code Duplication
            if ($this->max_depth && $o->depth >= $this->max_depth) {
116
                $array->hints[] = 'depth_limit';
117
118
                $this->applyPlugins($var, $array, self::TRIGGER_DEPTH_LIMIT);
119
120
                return $array;
121
            }
122
123
            // Don't even bother with reference checking below 5.2.2. It's an
124
            // absolute nightmare. The foreach loop depends on the array pointer
125
            // which "conveniently" moves about semi-randomly when you alter
126
            // the value you're looping over by means of a reference.
127
            if (KINT_PHP522) {
128
                $copy = array_values($var);
129
            }
130
131
            // It's really really hard to access numeric string keys in arrays,
132
            // and it's really really hard to access integer properties in
133
            // objects, so we just use array_values and index by counter to get
134
            // at it reliably for reference testing. This also affects access
135
            // paths since it's pretty much impossible to access these things
136
            // without complicated stuff you should never need to do.
137
            $i = 0;
138
139
            // Set the marker for recursion
140
            $var[$this->marker] = $array->depth;
141
142
            foreach ($var as $key => &$val) {
143
                if ($key === $this->marker) {
144
                    continue;
145
                }
146
147
                $child = new Kint_Object();
148
                $child->name = $key;
149
                $child->depth = $array->depth + 1;
150
                $child->access = Kint_Object::ACCESS_NONE;
151
                $child->operator = Kint_Object::OPERATOR_ARRAY;
152
153
                if ($array->access_path !== null) {
154
                    if (is_string($key) && (string) (int) $key === $key) {
155
                        $child->access_path = 'array_values('.$array->access_path.')['.$i.']';
156
                    } else {
157
                        $child->access_path = $array->access_path.'['.var_export($key, true).']';
158
                    }
159
                }
160
161 View Code Duplication
                if (KINT_PHP522) {
162
                    $stash = $val;
163
                    $copy[$i] = $this->marker;
164
                    if ($val === $this->marker) {
165
                        $child->reference = true;
166
                        $val = $stash;
167
                    }
168
                }
169
170
                $rep->contents[] = $this->parse($val, $child);
171
                ++$i;
172
            }
173
174
            $this->applyPlugins($var, $array, self::TRIGGER_SUCCESS);
175
            unset($var[$this->marker]);
176
177
            return $array;
178
        } else {
179
            $this->applyPlugins($var, $array, self::TRIGGER_SUCCESS);
180
181
            return $array;
182
        }
183
    }
184
185
    private function parseObject(&$var, Kint_Object $o)
186
    {
187 View Code Duplication
        if (KINT_PHP53 || function_exists('spl_object_hash')) {
188
            $hash = spl_object_hash($var);
189
        } else {
190
            ob_start();
191
            var_dump($var);
192
            preg_match('/#(\d+)/', ob_get_clean(), $match);
193
            $hash = $match[1];
194
        }
195
196
        $values = (array) $var;
197
198
        $object = $o->transplant(new Kint_Object_Instance());
199
        $object->classname = get_class($var);
200
        $object->hash = $hash;
201
        $object->size = count($values);
202
203 View Code Duplication
        if (isset($this->object_hashes[$hash])) {
204
            $object->hints[] = 'recursion';
205
206
            $this->applyPlugins($var, $object, self::TRIGGER_RECURSION);
207
208
            return $object;
209
        }
210
211
        $this->object_hashes[$hash] = $object;
212
213 View Code Duplication
        if ($this->max_depth && $o->depth >= $this->max_depth) {
214
            $object->hints[] = 'depth_limit';
215
216
            $this->applyPlugins($var, $object, self::TRIGGER_DEPTH_LIMIT);
217
            unset($this->object_hashes[$hash]);
218
219
            return $object;
220
        }
221
222
        // ArrayObject (and maybe ArrayIterator, did not try yet) unsurprisingly
223
        // consist of mainly dark magic. What bothers me most, var_dump sees no
224
        // problem with it, and ArrayObject also uses a custom, undocumented
225
        // serialize function, so you can see the properties in internal functions,
226
        // but can never iterate some of them if the flags are not STD_PROP_LIST. Fun stuff.
227
        if ($var instanceof ArrayObject) {
228
            $ArrayObject_flags_stash = $var->getFlags();
229
            $var->setFlags(ArrayObject::STD_PROP_LIST);
230
        }
231
232
        $reflector = new ReflectionObject($var);
233
234
        if ($reflector->isUserDefined()) {
235
            $object->filename = $reflector->getFileName();
236
            $object->startline = $reflector->getStartLine();
237
        }
238
239
        $rep = new Kint_Object_Representation('Properties');
240
241
        if (KINT_PHP522) {
242
            $copy = array_values($values);
243
        }
244
245
        $i = 0;
246
247
        // Reflection will not show parent classes private properties, and if a
248
        // property was unset it will happly trigger a notice looking for it.
249
        foreach ($values as $key => &$val) {
250
            // Casting object to array:
251
            // private properties show in the form "\0$owner_class_name\0$property_name";
252
            // protected properties show in the form "\0*\0$property_name";
253
            // public properties show in the form "$property_name";
254
            // http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
255
256
            $child = new Kint_Object();
257
            $child->depth = $object->depth + 1;
258
            $child->owner_class = $object->classname;
259
            $child->operator = Kint_Object::OPERATOR_OBJECT;
260
            $child->access = Kint_Object::ACCESS_PUBLIC;
261
262
            $split_key = explode("\0", $key, 3);
263
264
            if (count($split_key) === 3 && $split_key[0] === '') {
265
                $child->name = $split_key[2];
266
                if ($split_key[1] === '*') {
267
                    $child->access = Kint_Object::ACCESS_PROTECTED;
268
                } else {
269
                    $child->access = Kint_Object::ACCESS_PRIVATE;
270
                    $child->owner_class = $split_key[1];
271
                }
272
            } elseif (KINT_PHP72) {
273
                $child->name = (string) $key;
274
            } else {
275
                $child->name = $key;
276
            }
277
278
            if ($this->childHasPath($object, $child)) {
279
                $child->access_path = $object->access_path;
280
281
                if (!KINT_PHP72 && is_int($child->name)) {
282
                    $child->access_path = 'array_values((array) '.$child->access_path.')['.$i.']';
283
                } elseif (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $child->name)) {
284
                    $child->access_path .= '->'.$child->name;
285
                } else {
286
                    $child->access_path .= '->{'.var_export((string) $child->name, true).'}';
287
                }
288
            }
289
290 View Code Duplication
            if (KINT_PHP522) {
291
                $stash = $val;
292
                $copy[$i] = $this->marker;
293
                if ($val === $this->marker) {
294
                    $child->reference = true;
295
                    $val = $stash;
296
                }
297
            }
298
299
            $rep->contents[] = $this->parse($val, $child);
300
            ++$i;
301
        }
302
303
        if (isset($ArrayObject_flags_stash)) {
304
            $var->setFlags($ArrayObject_flags_stash);
305
        }
306
307
        usort($rep->contents, array('Kint_Parser', 'sortObjectProperties'));
308
309
        $object->addRepresentation($rep);
310
        $this->applyPlugins($var, $object, self::TRIGGER_SUCCESS);
311
        unset($this->object_hashes[$hash]);
312
313
        return $object;
314
    }
315
316
    private function parseResource(&$var, Kint_Object $o)
317
    {
318
        $resource = $o->transplant(new Kint_Object_Resource());
319
        $resource->resource_type = get_resource_type($var);
320
321
        $this->applyPlugins($var, $resource, self::TRIGGER_SUCCESS);
322
323
        return $resource;
324
    }
325
326
    private function parseUnknown(&$var, Kint_Object $o)
327
    {
328
        $o->type = 'unknown';
329
        $this->applyPlugins($var, $o, self::TRIGGER_SUCCESS);
330
331
        return $o;
332
    }
333
334
    public function addPlugin(Kint_Parser_Plugin $p)
335
    {
336
        if (!$types = $p->getTypes()) {
337
            return false;
338
        }
339
340
        if (!$triggers = $p->getTriggers()) {
341
            return false;
342
        }
343
344
        $p->setParser($this);
345
346
        foreach ($types as $type) {
347
            if (!isset($this->plugins[$type])) {
348
                $this->plugins[$type] = array(
349
                    self::TRIGGER_BEGIN => array(),
350
                    self::TRIGGER_SUCCESS => array(),
351
                    self::TRIGGER_RECURSION => array(),
352
                    self::TRIGGER_DEPTH_LIMIT => array(),
353
                );
354
            }
355
356
            foreach ($this->plugins[$type] as $trigger => &$pool) {
357
                if ($triggers & $trigger) {
358
                    $pool[] = $p;
359
                }
360
            }
361
        }
362
363
        return true;
364
    }
365
366
    public function clearPlugins()
367
    {
368
        $this->plugins = array();
369
    }
370
371
    /**
372
     * Applies plugins for an object type.
373
     *
374
     * @param mixed       &$var    variable
375
     * @param Kint_Object &$o      Kint object parsed so far
376
     * @param int         $trigger The trigger to check for the plugins
377
     *
378
     * @return bool Continue parsing
379
     */
380
    private function applyPlugins(&$var, Kint_Object &$o, $trigger)
381
    {
382
        $break_stash = $this->parse_break;
383
        $this->parse_break = false;
384
385
        $plugins = array();
386
387
        if (isset($this->plugins[$o->type][$trigger])) {
388
            $plugins = $this->plugins[$o->type][$trigger];
389
        }
390
391
        foreach ($plugins as $plugin) {
392
            try {
393
                $plugin->parse($var, $o, $trigger);
394
            } catch (Exception $e) {
395
                trigger_error(
396
                    'An exception ('.get_class($e).') was thrown in '.$e->getFile().' on line '.$e->getLine().' while executing Kint Parser Plugin "'.get_class($plugin).'". Error message: '.$e->getMessage(),
397
                    E_USER_WARNING
398
                );
399
            }
400
401
            if ($this->parse_break) {
402
                $this->parse_break = $break_stash;
403
404
                return false;
405
            }
406
        }
407
408
        $this->parse_break = $break_stash;
409
410
        return true;
411
    }
412
413
    public function haltParse()
414
    {
415
        $this->parse_break = true;
416
    }
417
418
    public function childHasPath(Kint_Object_Instance $parent, Kint_Object $child)
419
    {
420
        if ($parent->type === 'object' && ($parent->access_path !== null || $child->static || $child->const)) {
421
            if ($child->access === Kint_Object::ACCESS_PUBLIC) {
422
                return true;
423
            } elseif ($child->access === Kint_Object::ACCESS_PRIVATE && $this->caller_class && $this->caller_class === $child->owner_class) {
424
                // We can't accurately determine owner class on statics / consts below 5.3 so deny
425
                // the access path just to be sure. See ClassStatics for more info
426
                if (KINT_PHP53 || (!$child->static && !$child->const)) {
427
                    return true;
428
                }
429
            } elseif ($child->access === Kint_Object::ACCESS_PROTECTED && $this->caller_class) {
430
                if (is_a($this->caller_class, $child->owner_class, true) || is_a($child->owner_class, $this->caller_class, true)) {
431
                    return true;
432
                }
433
            }
434
        }
435
436
        return false;
437
    }
438
439
    /**
440
     * Returns an array without the recursion marker in it.
441
     *
442
     * DO NOT pass an array that has had it's marker removed back
443
     * into the parser, it will result in an extra recursion
444
     *
445
     * @param array $array Array potentially containing a recursion marker
446
     *
447
     * @return array Array with recursion marker removed
448
     */
449
    public function getCleanArray(array $array)
450
    {
451
        unset($array[$this->marker]);
452
453
        return $array;
454
    }
455
456
    private static function sortObjectProperties(Kint_Object $a, Kint_Object $b)
457
    {
458
        $sort = Kint_Object::sortByAccess($a, $b);
459
        if ($sort) {
460
            return $sort;
461
        }
462
463
        $sort = Kint_Object::sortByName($a, $b);
464
        if ($sort) {
465
            return $sort;
466
        }
467
468
        return Kint_Object_Instance::sortByHierarchy($a->owner_class, $b->owner_class);
469
    }
470
}
471