Passed
Push — master ( a0a923...0d8b13 )
by Jan
01:47
created

Debugger::getDebugInformationArray()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8.0368

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 22
cts 24
cp 0.9167
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 21
nc 4
nop 2
crap 8.0368
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 1
			'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
			if (php_sapi_name() == 'cli') {
313 1
				$result = '"' . (string) $data . '"';
314 1
			} else {
315
				$result = htmlentities($data);
316
				if ($data !== '' && $result === '') {
317
					$result = htmlentities(utf8_encode($data));
318
				}
319
320
				$result = '"' . (string) $result . '"';
321
			}
322 1
		} elseif (method_exists('\Xicrow\PhpDebug\Debugger', $methodName)) {
323 1
			$result = (string) self::$methodName($data, [
324 1
				'depth'  => ($options['depth'] - 1),
325 1
				'indent' => ($options['indent'] + 1),
326 1
			]);
327 1
		}
328
329 1
		return $result;
330
	}
331
332
	/**
333
	 * @return string
334
	 */
335 1
	private static function getDebugInformationNull() {
336 1
		return 'NULL';
337
	}
338
339
	/**
340
	 * @param boolean $data
341
	 *
342
	 * @return string
343
	 */
344 1
	private static function getDebugInformationBoolean($data) {
345 1
		return ($data ? 'TRUE' : 'FALSE');
346
	}
347
348
	/**
349
	 * @param integer $data
350
	 *
351
	 * @return string
352
	 */
353 1
	private static function getDebugInformationInteger($data) {
354 1
		return (string) $data;
355
	}
356
357
	/**
358
	 * @param double $data
359
	 *
360
	 * @return string
361
	 */
362 1
	private static function getDebugInformationDouble($data) {
363 1
		return (string) $data;
364
	}
365
366
	/**
367
	 * @param array $data
368
	 *
369
	 * @return string
370
	 */
371 1
	private static function getDebugInformationArray($data, array $options = []) {
372 1
		$options = array_merge([
373 1
			'depth'  => 25,
374 1
			'indent' => 0,
375 1
		], $options);
376
377 1
		$debugInfo = "[";
378
379 1
		$break = $end = null;
380 1
		if (!empty($data)) {
381 1
			$break = "\n" . str_repeat("\t", $options['indent']);
382 1
			$end   = "\n" . str_repeat("\t", $options['indent'] - 1);
383 1
		}
384
385 1
		$datas = [];
386 1
		if ($options['depth'] >= 0) {
387 1
			foreach ($data as $key => $val) {
388
				// Sniff for globals as !== explodes in < 5.4
389 1
				if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
390
					$val = '[recursion]';
391 1
				} elseif ($val !== $data) {
392 1
					$val = static::getDebugInformation($val, $options);
393 1
				}
394 1
				$datas[] = $break . static::getDebugInformation($key) . ' => ' . $val;
395 1
			}
396 1
		} else {
397
			$datas[] = $break . '[maximum depth reached]';
398
		}
399
400 1
		return $debugInfo . implode(',', $datas) . $end . ']';
401
	}
402
403
	/**
404
	 * @param object $data
405
	 *
406
	 * @return string
407
	 */
408 1
	private static function getDebugInformationObject($data, array $options = []) {
409 1
		$options = array_merge([
410 1
			'depth'  => 25,
411 1
			'indent' => 0,
412 1
		], $options);
413
414 1
		$debugInfo = '';
415 1
		$debugInfo .= 'object(' . get_class($data) . ') {';
416
417 1
		$break = "\n" . str_repeat("\t", $options['indent']);
418 1
		$end   = "\n" . str_repeat("\t", $options['indent'] - 1);
419
420 1
		if ($options['depth'] > 0 && method_exists($data, '__debugInfo')) {
421
			try {
422
				$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...
423
					'depth' => ($options['depth'] - 1),
424
				]));
425
				$debugInfo  .= substr($debugArray, 1, -1);
426
427
				return $debugInfo . $end . '}';
428
			} catch (\Exception $e) {
429
				$message = $e->getMessage();
430
431
				return $debugInfo . "\n(unable to export object: $message)\n }";
432
			}
433
		}
434
435 1
		if ($options['depth'] > 0) {
436 1
			$props      = [];
437 1
			$objectVars = get_object_vars($data);
438 1
			foreach ($objectVars as $key => $value) {
439 1
				$value   = static::getDebugInformation($value, array_merge($options, [
440 1
					'depth' => ($options['depth'] - 1),
441 1
				]));
442 1
				$props[] = "$key => " . $value;
443 1
			}
444
445 1
			$ref     = new \ReflectionObject($data);
446
			$filters = [
447 1
				\ReflectionProperty::IS_PROTECTED => 'protected',
448 1
				\ReflectionProperty::IS_PRIVATE   => 'private',
449 1
			];
450 1
			foreach ($filters as $filter => $visibility) {
451 1
				$reflectionProperties = $ref->getProperties($filter);
452 1
				foreach ($reflectionProperties as $reflectionProperty) {
453
					$reflectionProperty->setAccessible(true);
454
					$property = $reflectionProperty->getValue($data);
455
456
					$value   = static::getDebugInformation($property, array_merge($options, [
457
						'depth' => ($options['depth'] - 1),
458
					]));
459
					$key     = $reflectionProperty->name;
460
					$props[] = sprintf('[%s] %s => %s', $visibility, $key, $value);
461 1
				}
462 1
			}
463
464 1
			$debugInfo .= $break . implode($break, $props) . $end;
465 1
		}
466 1
		$debugInfo .= '}';
467
468 1
		return $debugInfo;
469
	}
470
471
	/**
472
	 * @param resource $data
473
	 *
474
	 * @return string
475
	 */
476 1
	private static function getDebugInformationResource($data) {
477 1
		return (string) $data . ' (' . get_resource_type($data) . ')';
478
	}
479
}
480