Passed
Push — master ( 3c48dd...03228a )
by Jan
03:59 queued 55s
created

Debugger   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 89.4%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 76
c 1
b 0
f 0
lcom 1
cbo 0
dl 0
loc 456
ccs 194
cts 217
cp 0.894
rs 5.488

16 Methods

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