Passed
Push — master ( 03228a...2c773f )
by Jan
02:36
created

Debugger   C

Complexity

Total Complexity 78

Size/Duplication

Total Lines 482
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 88.64%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 78
c 1
b 0
f 0
lcom 1
cbo 0
dl 0
loc 482
ccs 195
cts 220
cp 0.8864
rs 5.4563

16 Methods

Rating   Name   Duplication   Size   Complexity  
B output() 0 26 6
A debug() 0 8 1
B showTrace() 0 21 5
C reflectClass() 0 40 7
D reflectClassProperty() 0 34 8
D reflectClassMethod() 0 39 11
A getCalledFrom() 0 10 2
C getCalledFromTrace() 0 28 8
C getDebugInformation() 0 35 8
A getDebugInformationNull() 0 4 1
A getDebugInformationBoolean() 0 4 2
A getDebugInformationInteger() 0 4 1
A getDebugInformationDouble() 0 4 1
C getDebugInformationArray() 0 32 8
C getDebugInformationObject() 0 63 8
A getDebugInformationResource() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Debugger often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Debugger, and based on these observations, apply Extract Interface, too.

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
    public static $style = [
27
        '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>',
28
        '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>',
29
        'debug_null_format'    => '<span style="color: #B729D9;">%s</span>',
30
        'debug_boolean_format' => '<span style="color: #B729D9;">%s</span>',
31
        'debug_integer_format' => '<span style="color: #1299DA;">%s</span>',
32
        'debug_double_format'  => '<span style="color: #1299DA;">%s</span>',
33
        'debug_string_format'  => '<span style="color: #1299DA;">"</span>%s<span style="color: #1299DA;">"</span>',
34
    ];
35
36
    /**
37
     * @param string $data
38
     * @param array  $options
39
     *
40
     * @codeCoverageIgnore
41
     */
42
    public static function output($data, array $options = [])
43
    {
44
        $options = array_merge([
45
            'trace_offset' => 0,
46
        ], $options);
47
48
        if (!self::$output || !is_string($data)) {
49
            return;
50
        }
51
52
        if (php_sapi_name() == 'cli') {
53
            echo str_pad(' DEBUG ', 100, '-', STR_PAD_BOTH);
54
            echo "\n";
55
            if (self::$showCalledFrom) {
56
                echo self::getCalledFrom($options['trace_offset'] + 2);
57
                echo "\n";
58
            }
59
            echo $data;
60
            echo "\n";
61
        } else {
62
            if (self::$showCalledFrom) {
63
                echo sprintf(self::$style['called_from_format'], self::getCalledFrom($options['trace_offset'] + 2));
64
            }
65
            echo sprintf(self::$style['output_format'], $data);
66
        }
67
    }
68
69
    /**
70
     * @param mixed $data
71
     * @param array $options
72
     *
73
     * @codeCoverageIgnore
74
     */
75
    public static function debug($data, array $options = [])
76
    {
77
        $options = array_merge([
78
            'trace_offset' => 0,
79
        ], $options);
80
81
        self::output(self::getDebugInformation($data), $options);
82
    }
83
84
    /**
85
     * @param array $options
86
     *
87
     * @codeCoverageIgnore
88
     */
89
    public static function showTrace(array $options = [])
90
    {
91
        $options = array_merge([
92
            'trace_offset' => 0,
93
            'reverse'      => false,
94
        ], $options);
95
96
        $backtrace = ($options['reverse'] ? array_reverse(debug_backtrace()) : debug_backtrace());
97
98
        $output     = '';
99
        $traceIndex = ($options['reverse'] ? 1 : count($backtrace));
100
        foreach ($backtrace as $trace) {
101
            $output .= $traceIndex . ': ';
102
            $output .= self::getCalledFromTrace($trace);
103
            $output .= "\n";
104
105
            $traceIndex += ($options['reverse'] ? 1 : -1);
106
        }
107
108
        self::output($output);
109
    }
110
111
    /**
112
     * @param string $class
113
     * @param bool   $output
114
     *
115
     * @return string
116
     */
117 1
    public static function reflectClass($class, $output = true)
118
    {
119 1
        $data = '';
120
121 1
        $reflectionClass = new \ReflectionClass($class);
122
123 1
        $comment = $reflectionClass->getDocComment();
124 1
        if (!empty($comment)) {
125 1
            $data .= $comment;
126 1
            $data .= "\n";
127 1
        }
128
129 1
        $data         .= 'class ' . $reflectionClass->name . '{';
130 1
        $firstElement = true;
131 1
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
132 1
            if (!$firstElement) {
133 1
                $data .= "\n";
134 1
            }
135 1
            $firstElement = false;
136
137 1
            $data .= self::reflectClassProperty($class, $reflectionProperty->name, false);
138 1
        }
139
140 1
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
141 1
            if (!$firstElement) {
142 1
                $data .= "\n";
143 1
            }
144 1
            $firstElement = false;
145
146 1
            $data .= self::reflectClassMethod($class, $reflectionMethod->name, false);
147 1
        }
148 1
        $data .= "\n";
149 1
        $data .= '}';
150
151 1
        if ($output) {
152 1
            self::output($data);
153 1
        }
154
155 1
        return $data;
156
    }
157
158
    /**
159
     * @param string $class
160
     * @param string $property
161
     *
162
     * @return string
163
     */
164 2
    public static function reflectClassProperty($class, $property, $output = true)
165
    {
166 2
        $data = '';
167
168 2
        $reflectionClass    = new \ReflectionClass($class);
169 2
        $reflectionProperty = new \ReflectionProperty($class, $property);
170
171 2
        $defaultPropertyValues = $reflectionClass->getDefaultProperties();
172
173 2
        $comment = $reflectionProperty->getDocComment();
174 2
        if (!empty($comment)) {
175 2
            $data .= "\n";
176 2
            $data .= "\t";
177 2
            $data .= $comment;
178 2
        }
179
180 2
        $data .= "\n";
181 2
        $data .= "\t";
182 2
        $data .= ($reflectionProperty->isPublic() ? 'public ' : '');
183 2
        $data .= ($reflectionProperty->isPrivate() ? 'private ' : '');
184 2
        $data .= ($reflectionProperty->isProtected() ? 'protected ' : '');
185 2
        $data .= ($reflectionProperty->isStatic() ? 'static ' : '');
186 2
        $data .= '$' . $reflectionProperty->name;
187 2
        if (isset($defaultPropertyValues[$property])) {
188 2
            $data .= ' = ' . self::getDebugInformation($defaultPropertyValues[$property]);
189 2
        }
190 2
        $data .= ';';
191
192 2
        if ($output) {
193 1
            self::output($data);
194 1
        }
195
196 2
        return $data;
197
    }
198
199
    /**
200
     * @param string $class
201
     * @param string $method
202
     *
203
     * @return string
204
     */
205 2
    public static function reflectClassMethod($class, $method, $output = true)
206
    {
207 2
        $data = '';
208
209 2
        $reflectionMethod = new \ReflectionMethod($class, $method);
210
211 2
        $comment = $reflectionMethod->getDocComment();
212 2
        if (!empty($comment)) {
213 2
            $data .= "\n";
214 2
            $data .= "\t";
215 2
            $data .= $comment;
216 2
        }
217
218 2
        $data .= "\n";
219 2
        $data .= "\t";
220 2
        $data .= ($reflectionMethod->isPublic() ? 'public ' : '');
221 2
        $data .= ($reflectionMethod->isPrivate() ? 'private ' : '');
222 2
        $data .= ($reflectionMethod->isProtected() ? 'protected ' : '');
223 2
        $data .= ($reflectionMethod->isStatic() ? 'static ' : '');
224 2
        $data .= 'function ' . $reflectionMethod->name . '(';
225 2
        if ($reflectionMethod->getNumberOfParameters()) {
226 2
            foreach ($reflectionMethod->getParameters() as $reflectionMethodParameterIndex => $reflectionMethodParameter) {
227 2
                $data .= ($reflectionMethodParameterIndex > 0 ? ', ' : '');
228 2
                $data .= '$' . $reflectionMethodParameter->name;
229 2
                if ($reflectionMethodParameter->isDefaultValueAvailable()) {
230 2
                    $defaultValue = self::getDebugInformation($reflectionMethodParameter->getDefaultValue());
231 2
                    $defaultValue = str_replace(["\n", "\t"], '', $defaultValue);
232 2
                    $data         .= ' = ' . $defaultValue;
233 2
                }
234 2
            }
235 2
        }
236 2
        $data .= ') {}';
237
238 2
        if ($output) {
239 1
            self::output($data);
240 1
        }
241
242 2
        return $data;
243
    }
244
245
    /**
246
     * @param int $index
247
     *
248
     * @return string
249
     */
250 1
    public static function getCalledFrom($index = 0)
251
    {
252 1
        $backtrace = debug_backtrace();
253
254 1
        if (!isset($backtrace[$index])) {
255 1
            return 'Unknown trace with index: ' . $index;
256
        }
257
258 1
        return self::getCalledFromTrace($backtrace[$index]);
259
    }
260
261
    /**
262
     * @param array $trace
263
     *
264
     * @return string
265
     */
266 1
    public static function getCalledFromTrace($trace)
267
    {
268
        // Get file and line number
269 1
        $calledFromFile = '';
270 1
        if (isset($trace['file'])) {
271 1
            $calledFromFile .= $trace['file'] . ' line ' . $trace['line'];
272 1
            $calledFromFile = str_replace('\\', '/', $calledFromFile);
273 1
            $calledFromFile = (!empty(self::$documentRoot) ? substr($calledFromFile, strlen(self::$documentRoot)) : $calledFromFile);
274 1
            $calledFromFile = trim($calledFromFile, '/');
275 1
        }
276
277
        // Get function call
278 1
        $calledFromFunction = '';
279 1
        if (isset($trace['function'])) {
280 1
            $calledFromFunction .= (isset($trace['class']) ? $trace['class'] : '');
281 1
            $calledFromFunction .= (isset($trace['type']) ? $trace['type'] : '');
282 1
            $calledFromFunction .= $trace['function'] . '()';
283 1
        }
284
285
        // Return called from
286 1
        if ($calledFromFile) {
287 1
            return $calledFromFile;
288 1
        } elseif ($calledFromFunction) {
289 1
            return $calledFromFunction;
290
        } else {
291
            return 'Unable to get called from trace';
292
        }
293
    }
294
295
    /**
296
     * @param mixed $data
297
     *
298
     * @return string
299
     */
300 1
    public static function getDebugInformation($data, array $options = [])
301
    {
302 1
        $options = array_merge([
303 1
            'depth'  => 25,
304 1
            'indent' => 0,
305 1
        ], $options);
306
307 1
        $dataType = gettype($data);
308
309 1
        $methodName = 'getDebugInformation' . ucfirst(strtolower($dataType));
310
311 1
        $result = 'No method found supporting data type: ' . $dataType;
312 1
        if ($dataType == 'string') {
313 1
            if (php_sapi_name() == 'cli') {
314 1
                $result = '"' . (string)$data . '"';
315 1
            } else {
316
                $result = htmlentities($data);
317
                if ($data !== '' && $result === '') {
318
                    $result = htmlentities(utf8_encode($data));
319
                }
320
321
                $result = sprintf(self::$style['debug_string_format'], (string)$result);
322
            }
323 1
        } elseif (method_exists('\Xicrow\PhpDebug\Debugger', $methodName)) {
324 1
            $result = (string)self::$methodName($data, [
325 1
                'depth'  => ($options['depth'] - 1),
326 1
                'indent' => ($options['indent'] + 1),
327 1
            ]);
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 1
        }
332
333 1
        return $result;
334
    }
335
336
    /**
337
     * @return string
338
     */
339 1
    private static function getDebugInformationNull()
340
    {
341 1
        return 'NULL';
342
    }
343
344
    /**
345
     * @param boolean $data
346
     *
347
     * @return string
348
     */
349 1
    private static function getDebugInformationBoolean($data)
350
    {
351 1
        return ($data ? 'TRUE' : 'FALSE');
352
    }
353
354
    /**
355
     * @param integer $data
356
     *
357
     * @return string
358
     */
359 1
    private static function getDebugInformationInteger($data)
360
    {
361 1
        return (string)$data;
362
    }
363
364
    /**
365
     * @param double $data
366
     *
367
     * @return string
368
     */
369 1
    private static function getDebugInformationDouble($data)
370
    {
371 1
        return (string)$data;
372
    }
373
374
    /**
375
     * @param array $data
376
     *
377
     * @return string
378
     */
379 1
    private static function getDebugInformationArray($data, array $options = [])
380
    {
381 1
        $options = array_merge([
382 1
            'depth'  => 25,
383 1
            'indent' => 0,
384 1
        ], $options);
385
386 1
        $debugInfo = "[";
387
388 1
        $break = $end = null;
389 1
        if (!empty($data)) {
390 1
            $break = "\n" . str_repeat("\t", $options['indent']);
391 1
            $end   = "\n" . str_repeat("\t", $options['indent'] - 1);
392 1
        }
393
394 1
        $datas = [];
395 1
        if ($options['depth'] >= 0) {
396 1
            foreach ($data as $key => $val) {
397
                // Sniff for globals as !== explodes in < 5.4
398 1
                if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
399
                    $val = '[recursion]';
400 1
                } elseif ($val !== $data) {
401 1
                    $val = static::getDebugInformation($val, $options);
402 1
                }
403 1
                $datas[] = $break . static::getDebugInformation($key) . ' => ' . $val;
404 1
            }
405 1
        } else {
406
            $datas[] = $break . '[maximum depth reached]';
407
        }
408
409 1
        return $debugInfo . implode(',', $datas) . $end . ']';
410
    }
411
412
    /**
413
     * @param object $data
414
     *
415
     * @return string
416
     */
417 1
    private static function getDebugInformationObject($data, array $options = [])
418
    {
419 1
        $options = array_merge([
420 1
            'depth'  => 25,
421 1
            'indent' => 0,
422 1
        ], $options);
423
424 1
        $debugInfo = '';
425 1
        $debugInfo .= 'object(' . get_class($data) . ') {';
426
427 1
        $break = "\n" . str_repeat("\t", $options['indent']);
428 1
        $end   = "\n" . str_repeat("\t", $options['indent'] - 1);
429
430 1
        if ($options['depth'] > 0 && method_exists($data, '__debugInfo')) {
431
            try {
432
                $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...
433
                    'depth' => ($options['depth'] - 1),
434
                ]));
435
                $debugInfo  .= substr($debugArray, 1, -1);
436
437
                return $debugInfo . $end . '}';
438
            } catch (\Exception $e) {
439
                $message = $e->getMessage();
440
441
                return $debugInfo . "\n(unable to export object: $message)\n }";
442
            }
443
        }
444
445 1
        if ($options['depth'] > 0) {
446 1
            $props      = [];
447 1
            $objectVars = get_object_vars($data);
448 1
            foreach ($objectVars as $key => $value) {
449 1
                $value   = static::getDebugInformation($value, array_merge($options, [
450 1
                    'depth' => ($options['depth'] - 1),
451 1
                ]));
452 1
                $props[] = "$key => " . $value;
453 1
            }
454
455 1
            $ref     = new \ReflectionObject($data);
456
            $filters = [
457 1
                \ReflectionProperty::IS_PROTECTED => 'protected',
458 1
                \ReflectionProperty::IS_PRIVATE   => 'private',
459 1
            ];
460 1
            foreach ($filters as $filter => $visibility) {
461 1
                $reflectionProperties = $ref->getProperties($filter);
462 1
                foreach ($reflectionProperties as $reflectionProperty) {
463
                    $reflectionProperty->setAccessible(true);
464
                    $property = $reflectionProperty->getValue($data);
465
466
                    $value   = static::getDebugInformation($property, array_merge($options, [
467
                        'depth' => ($options['depth'] - 1),
468
                    ]));
469
                    $key     = $reflectionProperty->name;
470
                    $props[] = sprintf('[%s] %s => %s', $visibility, $key, $value);
471 1
                }
472 1
            }
473
474 1
            $debugInfo .= $break . implode($break, $props) . $end;
475 1
        }
476 1
        $debugInfo .= '}';
477
478 1
        return $debugInfo;
479
    }
480
481
    /**
482
     * @param resource $data
483
     *
484
     * @return string
485
     */
486 1
    private static function getDebugInformationResource($data)
487
    {
488 1
        return (string)$data . ' (' . get_resource_type($data) . ')';
489
    }
490
}
491