Passed
Push — master ( cb0b56...17a6b3 )
by Jan
03:22
created

Debugger   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 462
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 91.3%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 74
c 3
b 0
f 0
lcom 1
cbo 0
dl 0
loc 462
ccs 189
cts 207
cp 0.913
rs 5.5244

16 Methods

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