Passed
Push — master ( cb8a97...c75785 )
by Jan
02:32
created

Debugger::getDebugInformation()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4.0218

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
ccs 16
cts 18
cp 0.8889
rs 8.5806
cc 4
eloc 15
nc 4
nop 2
crap 4.0218
1
<?php
2
namespace Xicrow\PhpDebug;
3
4
/**
5
 * Class Debugger
6
 *
7
 * @package Xicrow\PhpDebug
8
 */
9
class Debugger
10
{
11
    /**
12
     * @var string|null
13
     */
14
    public static $documentRoot = null;
15
16
    /**
17
     * @var bool
18
     */
19
    public static $showCalledFrom = true;
20
21
    /**
22
     * @var bool
23
     */
24
    public static $output = true;
25
26
    /**
27
     * @var array
28
     */
29
    public static $style = [
30
        'output_format'        => '<pre style="margin-top: 0; padding: 5px; font-family: Menlo, Monaco, Consolas, monospace; font-weight: bold; font-size: 12px; background-color: #18171B; border: none; color: #FF8400; display: block; z-index: 1000;">%s</pre>',
31
        'called_from_format'   => '<pre style="margin-bottom: 0; padding: 5px; font-family: Menlo, Monaco, Consolas, monospace; font-weight: normal; font-size: 12px; background-color: #18171B; border: none; color: #AAAAAA; display: block; z-index: 1000;">%s</pre>',
32
        'debug_null_format'    => '<span style="color: #B729D9;">%s</span>',
33
        'debug_boolean_format' => '<span style="color: #B729D9;">%s</span>',
34
        'debug_integer_format' => '<span style="color: #1299DA;">%s</span>',
35
        'debug_double_format'  => '<span style="color: #1299DA;">%s</span>',
36
        'debug_string_format'  => '<span style="color: #1299DA;">"</span>%s<span style="color: #1299DA;">"</span>',
37
    ];
38
39
    /**
40
     * @param string $data
41
     * @param array  $options
42
     *
43
     * @codeCoverageIgnore
44
     */
45
    public static function output($data, array $options = [])
46
    {
47
        $options = array_merge([
48
            'trace_offset' => 0,
49
        ], $options);
50
51
        if (!self::$output || !is_string($data)) {
52
            return;
53
        }
54
55
        if (php_sapi_name() == 'cli') {
56
            echo str_pad(' DEBUG ', 100, '-', STR_PAD_BOTH);
57
            echo "\n";
58
            if (self::$showCalledFrom) {
59
                echo self::getCalledFrom($options['trace_offset'] + 2);
60
                echo "\n";
61
            }
62
            echo $data;
63
            echo "\n";
64
        } else {
65
            if (self::$showCalledFrom) {
66
                echo sprintf(self::$style['called_from_format'], self::getCalledFrom($options['trace_offset'] + 2));
67
            }
68
            echo sprintf(self::$style['output_format'], $data);
69
        }
70
    }
71
72
    /**
73
     * @param mixed $data
74
     * @param array $options
75
     *
76
     * @codeCoverageIgnore
77
     */
78
    public static function debug($data, array $options = [])
79
    {
80
        $options = array_merge([
81
            'trace_offset' => 0,
82
        ], $options);
83
84
        self::output(self::getDebugInformation($data), $options);
85
    }
86
87
    /**
88
     * @param array $options
89
     *
90
     * @codeCoverageIgnore
91
     */
92
    public static function showTrace(array $options = [])
93
    {
94
        $options = array_merge([
95
            'trace_offset' => 0,
96
            'reverse'      => false,
97
        ], $options);
98
99
        $backtrace = ($options['reverse'] ? array_reverse(debug_backtrace()) : debug_backtrace());
100
101
        $output     = '';
102
        $traceIndex = ($options['reverse'] ? 1 : count($backtrace));
103
        foreach ($backtrace as $trace) {
104
            $output .= $traceIndex . ': ';
105
            $output .= self::getCalledFromTrace($trace);
106
            $output .= "\n";
107
108
            $traceIndex += ($options['reverse'] ? 1 : -1);
109
        }
110
111
        self::output($output, $options);
112
    }
113
114
    /**
115
     * @param string $class
116
     * @param bool   $output
117
     *
118
     * @return string
119
     */
120 1
    public static function reflectClass($class, $output = true)
121
    {
122 1
        $data = '';
123
124 1
        $reflectionClass = new \ReflectionClass($class);
125
126 1
        $comment = $reflectionClass->getDocComment();
127 1
        if (!empty($comment)) {
128 1
            $data .= $comment;
129 1
            $data .= "\n";
130 1
        }
131
132 1
        $data         .= 'class ' . $reflectionClass->name . '{';
133 1
        $firstElement = true;
134 1
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
135 1
            if (!$firstElement) {
136 1
                $data .= "\n";
137 1
            }
138 1
            $firstElement = false;
139
140 1
            $data .= self::reflectClassProperty($class, $reflectionProperty->name, false);
141 1
        }
142
143 1
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
144 1
            if (!$firstElement) {
145 1
                $data .= "\n";
146 1
            }
147 1
            $firstElement = false;
148
149 1
            $data .= self::reflectClassMethod($class, $reflectionMethod->name, false);
150 1
        }
151 1
        $data .= "\n";
152 1
        $data .= '}';
153
154 1
        if ($output) {
155 1
            self::output($data);
156 1
        }
157
158 1
        return $data;
159
    }
160
161
    /**
162
     * @param string $class
163
     * @param string $property
164
     *
165
     * @return string
166
     */
167 2
    public static function reflectClassProperty($class, $property, $output = true)
168
    {
169 2
        $data = '';
170
171 2
        $reflectionClass    = new \ReflectionClass($class);
172 2
        $reflectionProperty = new \ReflectionProperty($class, $property);
173
174 2
        $defaultPropertyValues = $reflectionClass->getDefaultProperties();
175
176 2
        $comment = $reflectionProperty->getDocComment();
177 2
        if (!empty($comment)) {
178 2
            $data .= "\n";
179 2
            $data .= "\t";
180 2
            $data .= $comment;
181 2
        }
182
183 2
        $data .= "\n";
184 2
        $data .= "\t";
185 2
        $data .= ($reflectionProperty->isPublic() ? 'public ' : '');
186 2
        $data .= ($reflectionProperty->isPrivate() ? 'private ' : '');
187 2
        $data .= ($reflectionProperty->isProtected() ? 'protected ' : '');
188 2
        $data .= ($reflectionProperty->isStatic() ? 'static ' : '');
189 2
        $data .= '$' . $reflectionProperty->name;
190 2
        if (isset($defaultPropertyValues[$property])) {
191 2
            $data .= ' = ' . self::getDebugInformation($defaultPropertyValues[$property]);
192 2
        }
193 2
        $data .= ';';
194
195 2
        if ($output) {
196 1
            self::output($data);
197 1
        }
198
199 2
        return $data;
200
    }
201
202
    /**
203
     * @param string $class
204
     * @param string $method
205
     * @param bool   $output
206
     *
207
     * @return string
208
     */
209 2
    public static function reflectClassMethod($class, $method, $output = true)
210
    {
211 2
        $data = '';
212
213 2
        $reflectionMethod = new \ReflectionMethod($class, $method);
214
215 2
        $comment = $reflectionMethod->getDocComment();
216 2
        if (!empty($comment)) {
217 2
            $data .= "\n";
218 2
            $data .= "\t";
219 2
            $data .= $comment;
220 2
        }
221
222 2
        $data .= "\n";
223 2
        $data .= "\t";
224 2
        $data .= ($reflectionMethod->isPublic() ? 'public ' : '');
225 2
        $data .= ($reflectionMethod->isPrivate() ? 'private ' : '');
226 2
        $data .= ($reflectionMethod->isProtected() ? 'protected ' : '');
227 2
        $data .= ($reflectionMethod->isStatic() ? 'static ' : '');
228 2
        $data .= 'function ' . $reflectionMethod->name . '(';
229 2
        if ($reflectionMethod->getNumberOfParameters()) {
230 2
            foreach ($reflectionMethod->getParameters() as $reflectionMethodParameterIndex => $reflectionMethodParameter) {
231 2
                $data .= ($reflectionMethodParameterIndex > 0 ? ', ' : '');
232 2
                $data .= '$' . $reflectionMethodParameter->name;
233 2
                if ($reflectionMethodParameter->isDefaultValueAvailable()) {
234 2
                    $defaultValue = self::getDebugInformation($reflectionMethodParameter->getDefaultValue());
235 2
                    $defaultValue = str_replace(["\n", "\t"], '', $defaultValue);
236 2
                    $data         .= ' = ' . $defaultValue;
237 2
                }
238 2
            }
239 2
        }
240 2
        $data .= ') {}';
241
242 2
        if ($output) {
243 1
            self::output($data);
244 1
        }
245
246 2
        return $data;
247
    }
248
249
    /**
250
     * @param int $index
251
     *
252
     * @return string
253
     */
254 1
    public static function getCalledFrom($index = 0)
255
    {
256 1
        $backtrace = debug_backtrace();
257
258 1
        if (!isset($backtrace[$index])) {
259 1
            return 'Unknown trace with index: ' . $index;
260
        }
261
262 1
        return self::getCalledFromTrace($backtrace[$index]);
263
    }
264
265
    /**
266
     * @param array $trace
267
     *
268
     * @return string
269
     */
270 1
    public static function getCalledFromTrace($trace)
271
    {
272
        // Get file and line number
273 1
        $calledFromFile = '';
274 1
        if (isset($trace['file'])) {
275 1
            $calledFromFile .= $trace['file'] . ' line ' . $trace['line'];
276 1
            $calledFromFile = str_replace('\\', '/', $calledFromFile);
277 1
            $calledFromFile = (!empty(self::$documentRoot) ? substr($calledFromFile, strlen(self::$documentRoot)) : $calledFromFile);
278 1
            $calledFromFile = trim($calledFromFile, '/');
279 1
        }
280
281
        // Get function call
282 1
        $calledFromFunction = '';
283 1
        if (isset($trace['function'])) {
284 1
            $calledFromFunction .= (isset($trace['class']) ? $trace['class'] : '');
285 1
            $calledFromFunction .= (isset($trace['type']) ? $trace['type'] : '');
286 1
            $calledFromFunction .= $trace['function'] . '()';
287 1
        }
288
289
        // Return called from
290 1
        if ($calledFromFile) {
291 1
            return $calledFromFile;
292 1
        } elseif ($calledFromFunction) {
293 1
            return $calledFromFunction;
294
        } else {
295
            return 'Unable to get called from trace';
296
        }
297
    }
298
299
    /**
300
     * @param mixed $data
301
     *
302
     * @return string
303
     */
304 1
    public static function getDebugInformation($data, array $options = [])
305
    {
306
        // Merge options with default options
307 1
        $options = array_merge([
308 1
            'depth'  => 25,
309 1
            'indent' => 0,
310 1
        ], $options);
311
312
        // Get data type
313 1
        $dataType = gettype($data);
314
315
        // Set name of method to get debug information for data
316 1
        $methodName = 'getDebugInformation' . ucfirst(strtolower($dataType));
317
318
        // Get result from debug information method
319 1
        $result = 'No method found supporting data type: ' . $dataType;
320 1
        if (method_exists('\Xicrow\PhpDebug\Debugger', $methodName)) {
321 1
            $result = (string)self::$methodName($data, [
322 1
                'depth'  => ($options['depth'] - 1),
323 1
                'indent' => ($options['indent'] + 1),
324 1
            ]);
325 1
        }
326
327
        // Format the result
328 1
        if (php_sapi_name() != 'cli' && !empty(self::$style['debug_' . strtolower($dataType) . '_format'])) {
329
            $result = sprintf(self::$style['debug_' . strtolower($dataType) . '_format'], $result);
330
        }
331
332
        // Return result
333 1
        return $result;
334
    }
335
336
    /**
337
     * @return string
338
     */
339 1
    private static function getDebugInformationString($data)
340
    {
341 1
        return (string)(php_sapi_name() == 'cli' ? '"' . $data . '"' : htmlentities($data));
342
    }
343
344
    /**
345
     * @return string
346
     */
347 1
    private static function getDebugInformationNull()
348
    {
349 1
        return 'NULL';
350
    }
351
352
    /**
353
     * @param boolean $data
354
     *
355
     * @return string
356
     */
357 1
    private static function getDebugInformationBoolean($data)
358
    {
359 1
        return ($data ? 'TRUE' : 'FALSE');
360
    }
361
362
    /**
363
     * @param integer $data
364
     *
365
     * @return string
366
     */
367 1
    private static function getDebugInformationInteger($data)
368
    {
369 1
        return (string)$data;
370
    }
371
372
    /**
373
     * @param double $data
374
     *
375
     * @return string
376
     */
377 1
    private static function getDebugInformationDouble($data)
378
    {
379 1
        return (string)$data;
380
    }
381
382
    /**
383
     * @param array $data
384
     *
385
     * @return string
386
     */
387 1
    private static function getDebugInformationArray($data, array $options = [])
388
    {
389 1
        $options = array_merge([
390 1
            'depth'  => 25,
391 1
            'indent' => 0,
392 1
        ], $options);
393
394 1
        $debugInfo = "[";
395
396 1
        $break = $end = null;
397 1
        if (!empty($data)) {
398 1
            $break = "\n" . str_repeat("\t", $options['indent']);
399 1
            $end   = "\n" . str_repeat("\t", $options['indent'] - 1);
400 1
        }
401
402 1
        $datas = [];
403 1
        if ($options['depth'] >= 0) {
404 1
            foreach ($data as $key => $val) {
405
                // Sniff for globals as !== explodes in < 5.4
406 1
                if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
407
                    $val = '[recursion]';
408 1
                } elseif ($val !== $data) {
409 1
                    $val = static::getDebugInformation($val, $options);
410 1
                }
411 1
                $datas[] = $break . static::getDebugInformation($key) . ' => ' . $val;
412 1
            }
413 1
        } else {
414
            $datas[] = $break . '[maximum depth reached]';
415
        }
416
417 1
        return $debugInfo . implode(',', $datas) . $end . ']';
418
    }
419
420
    /**
421
     * @param object $data
422
     *
423
     * @return string
424
     */
425 1
    private static function getDebugInformationObject($data, array $options = [])
426
    {
427 1
        $options = array_merge([
428 1
            'depth'  => 25,
429 1
            'indent' => 0,
430 1
        ], $options);
431
432 1
        $debugInfo = '';
433 1
        $debugInfo .= 'object(' . get_class($data) . ') {';
434
435 1
        $break = "\n" . str_repeat("\t", $options['indent']);
436 1
        $end   = "\n" . str_repeat("\t", $options['indent'] - 1);
437
438 1
        if ($options['depth'] > 0 && method_exists($data, '__debugInfo')) {
439
            try {
440
                $debugArray = static::getDebugInformationArray($data->__debugInfo(), array_merge($options, [
0 ignored issues
show
Bug introduced by
Since getDebugInformationArray() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getDebugInformationArray() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
441
                    'depth' => ($options['depth'] - 1),
442
                ]));
443
                $debugInfo  .= substr($debugArray, 1, -1);
444
445
                return $debugInfo . $end . '}';
446
            } catch (\Exception $e) {
447
                $message = $e->getMessage();
448
449
                return $debugInfo . "\n(unable to export object: $message)\n }";
450
            }
451
        }
452
453 1
        if ($options['depth'] > 0) {
454 1
            $props      = [];
455 1
            $objectVars = get_object_vars($data);
456 1
            foreach ($objectVars as $key => $value) {
457 1
                $value   = static::getDebugInformation($value, array_merge($options, [
458 1
                    'depth' => ($options['depth'] - 1),
459 1
                ]));
460 1
                $props[] = "$key => " . $value;
461 1
            }
462
463 1
            $ref     = new \ReflectionObject($data);
464
            $filters = [
465 1
                \ReflectionProperty::IS_PROTECTED => 'protected',
466 1
                \ReflectionProperty::IS_PRIVATE   => 'private',
467 1
            ];
468 1
            foreach ($filters as $filter => $visibility) {
469 1
                $reflectionProperties = $ref->getProperties($filter);
470 1
                foreach ($reflectionProperties as $reflectionProperty) {
471
                    $reflectionProperty->setAccessible(true);
472
                    $property = $reflectionProperty->getValue($data);
473
474
                    $value   = static::getDebugInformation($property, array_merge($options, [
475
                        'depth' => ($options['depth'] - 1),
476
                    ]));
477
                    $key     = $reflectionProperty->name;
478
                    $props[] = sprintf('[%s] %s => %s', $visibility, $key, $value);
479 1
                }
480 1
            }
481
482 1
            $debugInfo .= $break . implode($break, $props) . $end;
483 1
        }
484 1
        $debugInfo .= '}';
485
486 1
        return $debugInfo;
487
    }
488
489
    /**
490
     * @param resource $data
491
     *
492
     * @return string
493
     */
494 1
    private static function getDebugInformationResource($data)
495
    {
496 1
        return (string)$data . ' (' . get_resource_type($data) . ')';
497
    }
498
}
499