Dumper::exportClosure()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the Tracy (https://tracy.nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7
8
namespace Tracy;
9
10
11
/**
12
 * Dumps a variable.
13
 */
14
class Dumper
15
{
16
	const
17
		DEPTH = 'depth', // how many nested levels of array/object properties display (defaults to 4)
18
		TRUNCATE = 'truncate', // how truncate long strings? (defaults to 150)
19
		COLLAPSE = 'collapse', // collapse top array/object or how big are collapsed? (defaults to 14)
20
		COLLAPSE_COUNT = 'collapsecount', // how big array/object are collapsed? (defaults to 7)
21
		LOCATION = 'location', // show location string? (defaults to 0)
22
		OBJECT_EXPORTERS = 'exporters', // custom exporters for objects (defaults to Dumper::$objectexporters)
23
		LIVE = 'live', // will be rendered using JavaScript
24
		DEBUGINFO = 'debuginfo', // use magic method __debugInfo if exists (defaults to false)
25
		KEYS_TO_HIDE = 'keystohide'; // sensitive keys not displayed (defaults to [])
26
27
	const
28
		LOCATION_SOURCE = 0b0001, // shows where dump was called
29
		LOCATION_LINK = 0b0010, // appends clickable anchor
30
		LOCATION_CLASS = 0b0100; // shows where class is defined
31
32
	const
33
		HIDDEN_VALUE = '*****';
34
35
	/** @var array */
36
	public static $terminalColors = [
37
		'bool' => '1;33',
38
		'null' => '1;33',
39
		'number' => '1;32',
40
		'string' => '1;36',
41
		'array' => '1;31',
42
		'key' => '1;37',
43
		'object' => '1;31',
44
		'visibility' => '1;30',
45
		'resource' => '1;37',
46
		'indent' => '1;30',
47
	];
48
49
	/** @var array */
50
	public static $resources = [
51
		'stream' => 'stream_get_meta_data',
52
		'stream-context' => 'stream_context_get_options',
53
		'curl' => 'curl_getinfo',
54
	];
55
56
	/** @var array */
57
	public static $objectExporters = [
58
		'Closure' => 'Tracy\Dumper::exportClosure',
59
		'SplFileInfo' => 'Tracy\Dumper::exportSplFileInfo',
60
		'SplObjectStorage' => 'Tracy\Dumper::exportSplObjectStorage',
61
		'__PHP_Incomplete_Class' => 'Tracy\Dumper::exportPhpIncompleteClass',
62
	];
63
64
	/** @var string @internal */
65
	public static $livePrefix;
66
67
	/** @var array  */
68
	private static $liveStorage = [];
69
70
71
	/**
72
	 * Dumps variable to the output.
73
	 * @return mixed  variable
74
	 */
75
	public static function dump($var, array $options = null)
76
	{
77
		if (PHP_SAPI !== 'cli' && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()))) {
78
			echo self::toHtml($var, $options);
79
		} elseif (self::detectColors()) {
80
			echo self::toTerminal($var, $options);
81
		} else {
82
			echo self::toText($var, $options);
83
		}
84
		return $var;
85
	}
86
87
88
	/**
89
	 * Dumps variable to HTML.
90
	 * @return string
91
	 */
92
	public static function toHtml($var, array $options = null)
93
	{
94
		$options = (array) $options + [
95
			self::DEPTH => 4,
96
			self::TRUNCATE => 150,
97
			self::COLLAPSE => 14,
98
			self::COLLAPSE_COUNT => 7,
99
			self::OBJECT_EXPORTERS => null,
100
			self::DEBUGINFO => false,
101
			self::KEYS_TO_HIDE => [],
102
		];
103
		$loc = &$options[self::LOCATION];
104
		$loc = $loc === true ? ~0 : (int) $loc;
105
106
		$options[self::KEYS_TO_HIDE] = array_flip(array_map('strtolower', $options[self::KEYS_TO_HIDE]));
107
		$options[self::OBJECT_EXPORTERS] = (array) $options[self::OBJECT_EXPORTERS] + self::$objectExporters;
108
		uksort($options[self::OBJECT_EXPORTERS], function ($a, $b) {
109
			return $b === '' || (class_exists($a, false) && is_subclass_of($a, $b)) ? -1 : 1;
110
		});
111
112
		$live = !empty($options[self::LIVE]) && $var && (is_array($var) || is_object($var) || is_resource($var));
113
		list($file, $line, $code) = $loc ? self::findLocation() : null;
114
		$locAttrs = $file && $loc & self::LOCATION_SOURCE ? Helpers::formatHtml(
115
			' title="%in file % on line %" data-tracy-href="%"', "$code\n", $file, $line, Helpers::editorUri($file, $line)
116
		) : null;
117
118
		return '<pre class="tracy-dump' . ($live && $options[self::COLLAPSE] === true ? ' tracy-collapsed' : '') . '"'
119
			. $locAttrs
120
			. ($live ? " data-tracy-dump='" . json_encode(self::toJson($var, $options), JSON_HEX_APOS | JSON_HEX_AMP) . "'>" : '>')
121
			. ($live ? '' : self::dumpVar($var, $options))
122
			. ($file && $loc & self::LOCATION_LINK ? '<small>in ' . Helpers::editorLink($file, $line) . '</small>' : '')
123
			. "</pre>\n";
124
	}
125
126
127
	/**
128
	 * Dumps variable to plain text.
129
	 * @return string
130
	 */
131
	public static function toText($var, array $options = null)
132
	{
133
		return htmlspecialchars_decode(strip_tags(self::toHtml($var, $options)), ENT_QUOTES);
134
	}
135
136
137
	/**
138
	 * Dumps variable to x-terminal.
139
	 * @return string
140
	 */
141
	public static function toTerminal($var, array $options = null)
142
	{
143
		return htmlspecialchars_decode(strip_tags(preg_replace_callback('#<span class="tracy-dump-(\w+)">|</span>#', function ($m) {
144
			return "\033[" . (isset($m[1], self::$terminalColors[$m[1]]) ? self::$terminalColors[$m[1]] : '0') . 'm';
145
		}, self::toHtml($var, $options))), ENT_QUOTES);
146
	}
147
148
149
	/**
150
	 * Internal toHtml() dump implementation.
151
	 * @param  mixed  $var
152
	 * @param  array  $options
153
	 * @param  int  $level  recursion level
154
	 * @return string
155
	 */
156
	private static function dumpVar(&$var, array $options, $level = 0)
157
	{
158
		if (method_exists(__CLASS__, $m = 'dump' . gettype($var))) {
159
			return self::$m($var, $options, $level);
160
		} else {
161
			return "<span>unknown type</span>\n";
162
		}
163
	}
164
165
166
	private static function dumpNull()
167
	{
168
		return "<span class=\"tracy-dump-null\">null</span>\n";
169
	}
170
171
172
	private static function dumpBoolean(&$var)
173
	{
174
		return '<span class="tracy-dump-bool">' . ($var ? 'true' : 'false') . "</span>\n";
175
	}
176
177
178
	private static function dumpInteger(&$var)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
179
	{
180
		return "<span class=\"tracy-dump-number\">$var</span>\n";
181
	}
182
183
184
	private static function dumpDouble(&$var)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
185
	{
186
		$var = is_finite($var)
187
			? ($tmp = json_encode($var)) . (strpos($tmp, '.') === false ? '.0' : '')
188
			: str_replace('.0', '', var_export($var, true)); // workaround for PHP 7.0.2
189
		return "<span class=\"tracy-dump-number\">$var</span>\n";
190
	}
191
192
193
	private static function dumpString(&$var, $options)
194
	{
195
		return '<span class="tracy-dump-string">"'
196
			. Helpers::escapeHtml(self::encodeString($var, $options[self::TRUNCATE]))
197
			. '"</span>' . (strlen($var) > 1 ? ' (' . strlen($var) . ')' : '') . "\n";
198
	}
199
200
201
	private static function dumpArray(&$var, $options, $level)
202
	{
203
		static $marker;
204
		if ($marker === null) {
205
			$marker = uniqid("\x00", true);
206
		}
207
208
		$out = '<span class="tracy-dump-array">array</span> (';
209
210
		if (empty($var)) {
211
			return $out . ")\n";
212
213
		} elseif (isset($var[$marker])) {
214
			return $out . (count($var) - 1) . ") [ <i>RECURSION</i> ]\n";
215
216
		} elseif (!$options[self::DEPTH] || $level < $options[self::DEPTH]) {
217
			$collapsed = $level ? count($var) >= $options[self::COLLAPSE_COUNT]
218
				: (is_int($options[self::COLLAPSE]) ? count($var) >= $options[self::COLLAPSE] : $options[self::COLLAPSE]);
219
			$out = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">'
220
				. $out . count($var) . ")</span>\n<div" . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
221
			$var[$marker] = true;
222
			foreach ($var as $k => &$v) {
223
				if ($k !== $marker) {
224
					$hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]) ? self::HIDDEN_VALUE : null;
225
					$k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"';
226
					$out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
227
						. '<span class="tracy-dump-key">' . $k . '</span> => '
228
						. ($hide ? self::dumpString($hide, $options) : self::dumpVar($v, $options, $level + 1));
229
				}
230
			}
231
			unset($var[$marker]);
232
			return $out . '</div>';
233
234
		} else {
235
			return $out . count($var) . ") [ ... ]\n";
236
		}
237
	}
238
239
240
	private static function dumpObject(&$var, $options, $level)
241
	{
242
		$fields = self::exportObject($var, $options[self::OBJECT_EXPORTERS], $options[self::DEBUGINFO]);
243
244
		$editorAttributes = '';
245
		if ($options[self::LOCATION] & self::LOCATION_CLASS) {
246
			$rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var);
247
			$editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine());
248
			if ($editor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $editor of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
249
				$editorAttributes = Helpers::formatHtml(
250
					' title="Declared in file % on line %" data-tracy-href="%"',
251
					$rc->getFileName(),
252
					$rc->getStartLine(),
253
					$editor
254
				);
255
			}
256
		}
257
		$out = '<span class="tracy-dump-object"' . $editorAttributes . '>'
258
			. Helpers::escapeHtml(Helpers::getClass($var))
259
			. '</span> <span class="tracy-dump-hash">#' . substr(md5(spl_object_hash($var)), 0, 4) . '</span>';
260
261
		static $list = [];
262
263
		if (empty($fields)) {
264
			return $out . "\n";
265
266
		} elseif (in_array($var, $list, true)) {
267
			return $out . " { <i>RECURSION</i> }\n";
268
269
		} elseif (!$options[self::DEPTH] || $level < $options[self::DEPTH] || $var instanceof \Closure) {
270
			$collapsed = $level ? count($fields) >= $options[self::COLLAPSE_COUNT]
271
				: (is_int($options[self::COLLAPSE]) ? count($fields) >= $options[self::COLLAPSE] : $options[self::COLLAPSE]);
272
			$out = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">'
273
				. $out . "</span>\n<div" . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
274
			$list[] = $var;
275
			foreach ($fields as $k => &$v) {
276
				$vis = '';
277 View Code Duplication
				if (isset($k[0]) && $k[0] === "\x00") {
278
					$vis = ' <span class="tracy-dump-visibility">' . ($k[1] === '*' ? 'protected' : 'private') . '</span>';
279
					$k = substr($k, strrpos($k, "\x00") + 1);
280
				}
281
				$hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]) ? self::HIDDEN_VALUE : null;
282
				$k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . Helpers::escapeHtml(self::encodeString($k, $options[self::TRUNCATE])) . '"';
283
				$out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
284
					. '<span class="tracy-dump-key">' . $k . "</span>$vis => "
285
					. ($hide ? self::dumpString($hide, $options) : self::dumpVar($v, $options, $level + 1));
286
			}
287
			array_pop($list);
288
			return $out . '</div>';
289
290
		} else {
291
			return $out . " { ... }\n";
292
		}
293
	}
294
295
296
	private static function dumpResource(&$var, $options, $level)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
297
	{
298
		$type = get_resource_type($var);
299
		$out = '<span class="tracy-dump-resource">' . Helpers::escapeHtml($type) . ' resource</span> '
300
			. '<span class="tracy-dump-hash">#' . (int) $var . '</span>';
301
		if (isset(self::$resources[$type])) {
302
			$out = "<span class=\"tracy-toggle tracy-collapsed\">$out</span>\n<div class=\"tracy-collapsed\">";
303
			foreach (call_user_func(self::$resources[$type], $var) as $k => $v) {
304
				$out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
305
					. '<span class="tracy-dump-key">' . Helpers::escapeHtml($k) . '</span> => ' . self::dumpVar($v, $options, $level + 1);
306
			}
307
			return $out . '</div>';
308
		}
309
		return "$out\n";
310
	}
311
312
313
	/**
314
	 * @return mixed
315
	 */
316
	private static function toJson(&$var, $options, $level = 0)
317
	{
318
		if (is_bool($var) || $var === null || is_int($var)) {
319
			return $var;
320
321
		} elseif (is_float($var)) {
322
			return is_finite($var)
323
				? (strpos($tmp = json_encode($var), '.') ? $var : ['number' => "$tmp.0"])
324
				: ['type' => (string) $var];
325
326
		} elseif (is_string($var)) {
327
			return self::encodeString($var, $options[self::TRUNCATE]);
328
329
		} elseif (is_array($var)) {
330
			static $marker;
331
			if ($marker === null) {
332
				$marker = uniqid("\x00", true);
333
			}
334
			if (isset($var[$marker]) || $level >= $options[self::DEPTH]) {
335
				return [null];
336
			}
337
			$res = [];
338
			$var[$marker] = true;
339
			foreach ($var as $k => &$v) {
340
				if ($k !== $marker) {
341
					$hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]);
342
					$k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"';
343
					$res[] = [$k, $hide ? self::HIDDEN_VALUE : self::toJson($v, $options, $level + 1)];
344
				}
345
			}
346
			unset($var[$marker]);
347
			return $res;
348
349
		} elseif (is_object($var)) {
350
			$obj = &self::$liveStorage[spl_object_hash($var)];
351
			if ($obj && $obj['level'] <= $level) {
352
				return ['object' => $obj['id']];
353
			}
354
355
			$editorInfo = null;
356
			if ($options[self::LOCATION] & self::LOCATION_CLASS) {
357
				$rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var);
358
				$editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine());
359
				$editorInfo = $editor ? ['file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor] : null;
360
			}
361
			static $counter = 1;
362
			$obj = $obj ?: [
363
				'id' => self::$livePrefix . '0' . $counter++, // differentiate from resources
364
				'name' => Helpers::getClass($var),
365
				'editor' => $editorInfo,
366
				'level' => $level,
367
				'object' => $var,
368
			];
369
370
			if ($level < $options[self::DEPTH] || !$options[self::DEPTH]) {
371
				$obj['level'] = $level;
372
				$obj['items'] = [];
373
374
				foreach (self::exportObject($var, $options[self::OBJECT_EXPORTERS], $options[self::DEBUGINFO]) as $k => $v) {
375
					$vis = 0;
376 View Code Duplication
					if (isset($k[0]) && $k[0] === "\x00") {
377
						$vis = $k[1] === '*' ? 1 : 2;
378
						$k = substr($k, strrpos($k, "\x00") + 1);
379
					}
380
					$hide = is_string($k) && isset($options[self::KEYS_TO_HIDE][strtolower($k)]);
381
					$k = is_int($k) || preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"';
382
					$obj['items'][] = [$k, $hide ? self::HIDDEN_VALUE : self::toJson($v, $options, $level + 1), $vis];
383
				}
384
			}
385
			return ['object' => $obj['id']];
386
387
		} elseif (is_resource($var)) {
388
			$obj = &self::$liveStorage[(string) $var];
389
			if (!$obj) {
390
				$type = get_resource_type($var);
391
				$obj = ['id' => self::$livePrefix . (int) $var, 'name' => $type . ' resource'];
392
				if (isset(self::$resources[$type])) {
393
					foreach (call_user_func(self::$resources[$type], $var) as $k => $v) {
394
						$obj['items'][] = [$k, self::toJson($v, $options, $level + 1)];
395
					}
396
				}
397
			}
398
			return ['resource' => $obj['id']];
399
400
		} else {
401
			return ['type' => 'unknown type'];
402
		}
403
	}
404
405
406
	/** @return array  */
407
	public static function fetchLiveData()
408
	{
409
		$res = [];
410
		foreach (self::$liveStorage as $obj) {
411
			$id = $obj['id'];
412
			unset($obj['level'], $obj['object'], $obj['id']);
413
			$res[$id] = $obj;
414
		}
415
		self::$liveStorage = [];
416
		return $res;
417
	}
418
419
420
	/**
421
	 * @internal
422
	 * @return string UTF-8
423
	 */
424
	public static function encodeString($s, $maxLength = null)
425
	{
426
		static $table;
427
		if ($table === null) {
428
			foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
429
				$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
430
			}
431
			$table['\\'] = '\\\\';
432
			$table["\r"] = '\r';
433
			$table["\n"] = '\n';
434
			$table["\t"] = '\t';
435
		}
436
437
		if ($maxLength && strlen($s) > $maxLength) { // shortens to $maxLength in UTF-8 or longer
438
			if (function_exists('mb_substr')) {
439
				$s = mb_substr($tmp = $s, 0, $maxLength, 'UTF-8');
440
				$shortened = $s !== $tmp;
441
			} else {
442
				$i = $len = 0;
443
				$maxI = $maxLength * 4; // max UTF-8 length
444
				do {
445
					if (($s[$i] < "\x80" || $s[$i] >= "\xC0") && (++$len > $maxLength) || $i >= $maxI) {
446
						$s = substr($s, 0, $i);
447
						$shortened = true;
448
						break;
449
					}
450
				} while (isset($s[++$i]));
451
			}
452
		}
453
454
		if (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $s) || preg_last_error()) { // is binary?
455
			if ($maxLength && strlen($s) > $maxLength) {
456
				$s = substr($s, 0, $maxLength);
457
				$shortened = true;
458
			}
459
			$s = strtr($s, $table);
460
		}
461
462
		return $s . (empty($shortened) ? '' : ' ... ');
463
	}
464
465
466
	/**
467
	 * @return array
468
	 */
469
	private static function exportObject($obj, array $exporters, $useDebugInfo)
470
	{
471
		foreach ($exporters as $type => $dumper) {
472
			if (!$type || $obj instanceof $type) {
473
				return call_user_func($dumper, $obj);
474
			}
475
		}
476
477
		if ($useDebugInfo && method_exists($obj, '__debugInfo')) {
478
			return $obj->__debugInfo();
479
		}
480
481
		return (array) $obj;
482
	}
483
484
485
	/**
486
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|integer|string[]>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
487
	 */
488
	private static function exportClosure(\Closure $obj)
489
	{
490
		$rc = new \ReflectionFunction($obj);
491
		$res = [];
492
		foreach ($rc->getParameters() as $param) {
493
			$res[] = '$' . $param->getName();
494
		}
495
		return [
496
			'file' => $rc->getFileName(),
497
			'line' => $rc->getStartLine(),
498
			'variables' => $rc->getStaticVariables(),
499
			'parameters' => implode(', ', $res),
500
		];
501
	}
502
503
504
	/**
505
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
506
	 */
507
	private static function exportSplFileInfo(\SplFileInfo $obj)
508
	{
509
		return ['path' => $obj->getPathname()];
510
	}
511
512
513
	/**
514
	 * @return array
515
	 */
516
	private static function exportSplObjectStorage(\SplObjectStorage $obj)
517
	{
518
		$res = [];
519
		foreach (clone $obj as $item) {
520
			$res[] = ['object' => $item, 'data' => $obj[$item]];
521
		}
522
		return $res;
523
	}
524
525
526
	/**
527
	 * @return array
528
	 */
529
	private static function exportPhpIncompleteClass(\__PHP_Incomplete_Class $obj)
530
	{
531
		$info = ['className' => null, 'private' => [], 'protected' => [], 'public' => []];
532
		foreach ((array) $obj as $name => $value) {
533
			if ($name === '__PHP_Incomplete_Class_Name') {
534
				$info['className'] = $value;
535
			} elseif (preg_match('#^\x0\*\x0(.+)\z#', $name, $m)) {
536
				$info['protected'][$m[1]] = $value;
537
			} elseif (preg_match('#^\x0(.+)\x0(.+)\z#', $name, $m)) {
538
				$info['private'][$m[1] . '::$' . $m[2]] = $value;
539
			} else {
540
				$info['public'][$name] = $value;
541
			}
542
		}
543
		return $info;
544
	}
545
546
547
	/**
548
	 * Finds the location where dump was called.
549
	 * @return array|null [file, line, code]
550
	 */
551
	private static function findLocation()
552
	{
553
		foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
554
			if (isset($item['class']) && $item['class'] === __CLASS__) {
555
				$location = $item;
556
				continue;
557
			} elseif (isset($item['function'])) {
558
				try {
559
					$reflection = isset($item['class'])
560
						? new \ReflectionMethod($item['class'], $item['function'])
561
						: new \ReflectionFunction($item['function']);
562
					if ($reflection->isInternal() || preg_match('#\s@tracySkipLocation\s#', (string) $reflection->getDocComment())) {
563
						$location = $item;
564
						continue;
565
					}
566
				} catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
567
				}
568
			}
569
			break;
570
		}
571
572
		if (isset($location['file'], $location['line']) && is_file($location['file'])) {
573
			$lines = file($location['file']);
574
			$line = $lines[$location['line'] - 1];
575
			return [
576
				$location['file'],
577
				$location['line'],
578
				trim(preg_match('#\w*dump(er::\w+)?\(.*\)#i', $line, $m) ? $m[0] : $line),
579
			];
580
		}
581
	}
582
583
584
	/**
585
	 * @return bool
586
	 */
587
	private static function detectColors()
588
	{
589
		return self::$terminalColors &&
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$terminalColors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
590
			(getenv('ConEmuANSI') === 'ON'
591
			|| getenv('ANSICON') !== false
592
			|| getenv('term') === 'xterm-256color'
593
			|| (defined('STDOUT') && function_exists('posix_isatty') && posix_isatty(STDOUT)));
594
	}
595
}
596