Passed
Push — master ( fa6eb7...3c48dd )
by Jan
03:35
created

Debugger::output()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 29
ccs 0
cts 0
cp 0
rs 8.439
cc 6
eloc 22
nc 5
nop 2
crap 42
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
	public static function reflectClass($class, $output = true) {
108
		$data = '';
109
110
		$reflectionClass = new \ReflectionClass($class);
111
112
		$comment = $reflectionClass->getDocComment();
113
		if (!empty($comment)) {
114
			$data .= $comment;
115
			$data .= "\n";
116
		}
117
118
		$data         .= 'class ' . $reflectionClass->name . '{';
119
		$firstElement = true;
120
		foreach ($reflectionClass->getProperties() as $reflectionProperty) {
121
			if (!$firstElement) {
122
				$data .= "\n";
123
			}
124
			$firstElement = false;
125
126
			$data .= self::reflectClassProperty($class, $reflectionProperty->name, false);
127
		}
128
129
		foreach ($reflectionClass->getMethods() as $reflectionMethod) {
130
			if (!$firstElement) {
131
				$data .= "\n";
132
			}
133
			$firstElement = false;
134
135
			$data .= self::reflectClassMethod($class, $reflectionMethod->name, false);
136
		}
137
		$data .= "\n";
138
		$data .= '}';
139
140
		if ($output) {
141
			self::output($data);
142
		}
143
144
		return $data;
145
	}
146
147
	/**
148
	 * @param string $class
149
	 * @param string $property
150
	 *
151
	 * @return string
152
	 */
153
	public static function reflectClassProperty($class, $property, $output = true) {
154
		$data = '';
155
156
		$reflectionClass    = new \ReflectionClass($class);
157
		$reflectionProperty = new \ReflectionProperty($class, $property);
158
159
		$defaultPropertyValues = $reflectionClass->getDefaultProperties();
160
161
		$comment = $reflectionProperty->getDocComment();
162
		if (!empty($comment)) {
163
			$data .= "\n";
164
			$data .= "\t";
165
			$data .= $comment;
166
		}
167
168
		$data .= "\n";
169
		$data .= "\t";
170
		$data .= ($reflectionProperty->isPublic() ? 'public ' : '');
171
		$data .= ($reflectionProperty->isPrivate() ? 'private ' : '');
172
		$data .= ($reflectionProperty->isProtected() ? 'protected ' : '');
173
		$data .= ($reflectionProperty->isStatic() ? 'static ' : '');
174
		$data .= '$' . $reflectionProperty->name;
175
		if (isset($defaultPropertyValues[$property])) {
176
			$data .= ' = ' . self::getDebugInformation($defaultPropertyValues[$property]);
177
		}
178
		$data .= ';';
179
180
		if ($output) {
181
			self::output($data);
182
		}
183
184
		return $data;
185
	}
186
187
	/**
188
	 * @param string $class
189
	 * @param string $method
190
	 *
191
	 * @return string
192
	 */
193
	public static function reflectClassMethod($class, $method, $output = true) {
194
		$data = '';
195
196
		$reflectionMethod = new \ReflectionMethod($class, $method);
197
198
		$comment = $reflectionMethod->getDocComment();
199
		if (!empty($comment)) {
200
			$data .= "\n";
201
			$data .= "\t";
202
			$data .= $comment;
203
		}
204
205
		$data .= "\n";
206
		$data .= "\t";
207
		$data .= ($reflectionMethod->isPublic() ? 'public ' : '');
208
		$data .= ($reflectionMethod->isPrivate() ? 'private ' : '');
209
		$data .= ($reflectionMethod->isProtected() ? 'protected ' : '');
210
		$data .= ($reflectionMethod->isStatic() ? 'static ' : '');
211
		$data .= 'function ' . $reflectionMethod->name . '(';
212
		if ($reflectionMethod->getNumberOfParameters()) {
213
			foreach ($reflectionMethod->getParameters() as $reflectionMethodParameterIndex => $reflectionMethodParameter) {
214
				$data .= ($reflectionMethodParameterIndex > 0 ? ', ' : '');
215
				$data .= '$' . $reflectionMethodParameter->name;
216
				if ($reflectionMethodParameter->isDefaultValueAvailable()) {
217
					$defaultValue = self::getDebugInformation($reflectionMethodParameter->getDefaultValue());
218
					$defaultValue = str_replace(["\n", "\t"], '', $defaultValue);
219
					$data         .= ' = ' . $defaultValue;
220
				}
221
			}
222
		}
223
		$data .= ') {}';
224
225
		if ($output) {
226
			self::output($data);
227
		}
228
229
		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
	public static function getDebugInformation($data, array $options = []) {
286
		$options = array_merge([
287
			'depth'  => 25,
288
			'indent' => 0,
289
		], $options);
290
291
		$dataType = gettype($data);
292
293
		$methodName = 'getDebugInformation' . ucfirst(strtolower($dataType));
294
295
		$result = 'No method found supporting data type: ' . $dataType;
296
		if ($dataType == 'string') {
297
			if (php_sapi_name() == 'cli') {
298
				$result = '"' . (string) $data . '"';
299
			} 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
		} elseif (method_exists('\Xicrow\PhpDebug\Debugger', $methodName)) {
308
			$result = (string) self::$methodName($data, [
309
				'depth'  => ($options['depth'] - 1),
310
				'indent' => ($options['indent'] + 1),
311
			]);
312
		}
313
314
		return $result;
315
	}
316
317
	/**
318
	 * @return string
319
	 */
320
	private static function getDebugInformationNull() {
321
		return 'NULL';
322
	}
323
324
	/**
325
	 * @param boolean $data
326
	 *
327
	 * @return string
328
	 */
329
	private static function getDebugInformationBoolean($data) {
330
		return ($data ? 'TRUE' : 'FALSE');
331
	}
332
333
	/**
334
	 * @param integer $data
335
	 *
336
	 * @return string
337
	 */
338
	private static function getDebugInformationInteger($data) {
339
		return (string) $data;
340
	}
341
342
	/**
343
	 * @param double $data
344
	 *
345
	 * @return string
346
	 */
347
	private static function getDebugInformationDouble($data) {
348
		return (string) $data;
349
	}
350
351
	/**
352
	 * @param array $data
353
	 *
354
	 * @return string
355
	 */
356
	private static function getDebugInformationArray($data, array $options = []) {
357
		$options = array_merge([
358
			'depth'  => 25,
359
			'indent' => 0,
360
		], $options);
361
362
		$debugInfo = "[";
363
364
		$break = $end = null;
365
		if (!empty($data)) {
366
			$break = "\n" . str_repeat("\t", $options['indent']);
367
			$end   = "\n" . str_repeat("\t", $options['indent'] - 1);
368
		}
369
370
		$datas = [];
371
		if ($options['depth'] >= 0) {
372
			foreach ($data as $key => $val) {
373
				// Sniff for globals as !== explodes in < 5.4
374
				if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
375
					$val = '[recursion]';
376
				} elseif ($val !== $data) {
377
					$val = static::getDebugInformation($val, $options);
378
				}
379
				$datas[] = $break . static::getDebugInformation($key) . ' => ' . $val;
380
			}
381
		} else {
382
			$datas[] = $break . '[maximum depth reached]';
383
		}
384
385
		return $debugInfo . implode(',', $datas) . $end . ']';
386
	}
387
388
	/**
389
	 * @param object $data
390
	 *
391
	 * @return string
392
	 */
393
	private static function getDebugInformationObject($data, array $options = []) {
394
		$options = array_merge([
395
			'depth'  => 25,
396
			'indent' => 0,
397
		], $options);
398
399
		$debugInfo = '';
400
		$debugInfo .= 'object(' . get_class($data) . ') {';
401
402
		$break = "\n" . str_repeat("\t", $options['indent']);
403
		$end   = "\n" . str_repeat("\t", $options['indent'] - 1);
404
405
		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
		if ($options['depth'] > 0) {
421
			$props      = [];
422
			$objectVars = get_object_vars($data);
423
			foreach ($objectVars as $key => $value) {
424
				$value   = static::getDebugInformation($value, array_merge($options, [
425
					'depth' => ($options['depth'] - 1),
426
				]));
427
				$props[] = "$key => " . $value;
428
			}
429
430
			$ref     = new \ReflectionObject($data);
431
			$filters = [
432
				\ReflectionProperty::IS_PROTECTED => 'protected',
433
				\ReflectionProperty::IS_PRIVATE   => 'private',
434
			];
435
			foreach ($filters as $filter => $visibility) {
436
				$reflectionProperties = $ref->getProperties($filter);
437
				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
				}
447
			}
448
449
			$debugInfo .= $break . implode($break, $props) . $end;
450
		}
451
		$debugInfo .= '}';
452
453
		return $debugInfo;
454
	}
455
456
	/**
457
	 * @param resource $data
458
	 *
459
	 * @return string
460
	 */
461
	private static function getDebugInformationResource($data) {
462
		return (string) $data . ' (' . get_resource_type($data) . ')';
463
	}
464
}
465