Passed
Push — master ( c43d65...cb0b56 )
by Jan
02:53
created

Debugger   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 437
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 91.75%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 73
c 2
b 0
f 0
lcom 1
cbo 0
dl 0
loc 437
ccs 189
cts 206
cp 0.9175
rs 5.5447

16 Methods

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