Passed
Push — master ( 17a6b3...eba544 )
by Jan
02:38
created

Timer::showAll()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 40
Code Lines 27

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 40
ccs 0
cts 0
cp 0
rs 8.439
cc 6
eloc 27
nc 8
nop 1
crap 42
1
<?php
2
namespace Xicrow\PhpDebug;
3
4
/**
5
 * Class Timer
6
 *
7
 * @package Xicrow\PhpDebug
8
 */
9
class Timer {
10
	/**
11
	 * @var array
12
	 */
13
	public static $collection = [];
14
15
	/**
16
	 * @var boolean|string
17
	 */
18
	public static $currentItem = false;
19
20
	/**
21
	 * @var array
22
	 */
23
	public static $runningItems = [];
24
25
	/**
26
	 * Force the unit to display elapsed times in (MS|S|M|H|D|W)
27
	 *
28
	 * @var null|string
29
	 */
30
	public static $forceDisplayUnit = null;
31
32
	/**
33
	 * Color threshold for output (5 => 'red', all items with values of 5 or higher will be red)
34
	 *
35
	 * @var array
36
	 */
37
	public static $colorThreshold = [];
38
39
	/**
40
	 *
41
	 */
42 6
	public static function reset() {
43 6
		self::$collection   = [];
44 6
		self::$currentItem  = false;
45 6
		self::$runningItems = [];
46 6
	}
47
48
	/**
49
	 * @param string|null $key
50
	 * @param array       $data
51
	 *
52
	 * @return string
53
	 */
54 6
	public static function add($key = null, $data = []) {
55
		// If no key is given
56 6
		if (is_null($key)) {
57
			// Set key to file and line
58
			$key = Debugger::getCalledFrom(2);
59
		}
60
61
		// If key is allready in use
62 6
		if (isset(self::$collection[$key])) {
63
			// Get original item
64 1
			$item = self::$collection[$key];
65
66
			// Set new item count
67 1
			$itemCount = (isset($item['count']) ? ($item['count'] + 1) : 2);
68
69
			// Set correct key for the original item
70 1
			if (strpos($item['key'], '#') === false) {
71 1
				self::$collection[$key] = array_merge($item, [
72 1
					'key'   => $key . ' #1',
73
					'count' => $itemCount
74 1
				]);
75 1
			} else {
76
				self::$collection[$key] = array_merge($item, [
77
					'count' => $itemCount
78
				]);
79
			}
80
81
			// Set new key
82 1
			$key = $key . ' #' . $itemCount;
83 1
		}
84
85
		// Make sure various options are set
86 6
		if (!isset($data['key'])) {
87 6
			$data['key'] = $key;
88 6
		}
89 6
		if (!isset($data['parent'])) {
90 6
			$data['parent'] = self::$currentItem;
91 6
		}
92 6
		if (!isset($data['level'])) {
93 6
			$data['level'] = 0;
94 6
			if (isset($data['parent']) && isset(self::$collection[$data['parent']])) {
95 1
				$data['level'] = (self::$collection[$data['parent']]['level'] + 1);
96 1
			}
97 6
		}
98
99
		// Add item to collection
100 6
		self::$collection[$key] = $data;
101
102 6
		return $key;
103
	}
104
105
	/**
106
	 * @param string|null $key
107
	 *
108
	 * @return string
109
	 */
110 3
	public static function start($key = null) {
111
		// Add new item
112 3
		$key = self::add($key, [
113 3
			'start' => microtime(true)
114 3
		]);
115
116
		// Set current item
117 3
		self::$currentItem = $key;
118
119
		// Add to running items
120 3
		self::$runningItems[$key] = true;
121
122 3
		return $key;
123
	}
124
125
	/**
126
	 * @param string|null $key
127
	 *
128
	 * @return string
129
	 */
130 2
	public static function stop($key = null) {
131
		// If no key is given
132 2
		if (is_null($key)) {
133
			// Get key of the last started item
134 1
			end(self::$runningItems);
135 1
			$key = key(self::$runningItems);
136 1
		}
137
138
		// Check for key duplicates, and find the first one not stopped
139 2
		if (isset(self::$collection[$key]) && isset(self::$collection[$key]['stop'])) {
140 1
			$originalName = $key;
141 1
			$i            = 1;
142 1
			while (isset(self::$collection[$key])) {
143 1
				if (!isset(self::$collection[$key]['stop'])) {
144 1
					break;
145
				}
146
147 1
				$key = $originalName . ' #' . ($i + 1);
148
149 1
				$i++;
150 1
			}
151 1
		}
152
153
		// If item exists in collection
154 2
		if (isset(self::$collection[$key])) {
155
			// Update the item
156 2
			self::$collection[$key]['stop'] = microtime(true);
157
158 2
			self::$currentItem = self::$collection[$key]['parent'];
159 2
		}
160
161 2
		if (isset(self::$runningItems[$key])) {
162 2
			unset(self::$runningItems[$key]);
163 2
		}
164
165 2
		return $key;
166
	}
167
168
	/**
169
	 * @param string|null    $key
170
	 * @param int|float|null $start
171
	 * @param int|float|null $stop
172
	 *
173
	 * @return string
174
	 */
175 2
	public static function custom($key = null, $start = null, $stop = null) {
176
		// Add new item
177 2
		self::add($key, [
178 2
			'start' => $start,
179
			'stop'  => $stop
180 2
		]);
181
182
		// If no stop value is given
183 2
		if (is_null($stop)) {
184
			// Set current item
185 1
			self::$currentItem = $key;
186
187
			// Add to running items
188 1
			self::$runningItems[$key] = true;
189 1
		}
190
191 2
		return $key;
192
	}
193
194
	/**
195
	 * @param string|null           $key
196
	 * @param string|array|\Closure $callback
197
	 *
198
	 * @return mixed
199
	 */
200 1
	public static function callback($key = null, $callback) {
201
		// Get parameters for callback
202 1
		$callbackParams = func_get_args();
203 1
		unset($callbackParams[0], $callbackParams[1]);
204 1
		$callbackParams = array_values($callbackParams);
205
206
		// Get key if no key is given
207 1
		if (is_null($key)) {
208 1
			if (is_string($callback)) {
209 1
				$key = $callback;
210 1
			} elseif (is_array($callback)) {
211 1
				$keyArr = [];
212 1
				foreach ($callback as $k => $v) {
213 1
					if (is_string($v)) {
214 1
						$keyArr[] = $v;
215 1
					} elseif (is_object($v)) {
216 1
						$keyArr[] = get_class($v);
217 1
					}
218 1
				}
219
220 1
				$key = implode('', $keyArr);
221 1
				if (count($keyArr) > 1) {
222 1
					$method = array_pop($keyArr);
223 1
					$key    = implode('/', $keyArr);
224 1
					$key .= '::' . $method;
225 1
				}
226
227 1
				unset($keyArr, $method);
228 1
			} elseif (is_object($callback) && $callback instanceof \Closure) {
229 1
				$key = 'closure';
230 1
			}
231 1
			$key = 'callback: ' . $key;
232 1
		}
233
234
		// Set default return value
235 1
		$returnValue = true;
236
237
		// Set error handler, to convert errors to exceptions
238 1
		set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext) {
0 ignored issues
show
Unused Code introduced by
The parameter $errcontext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
239 1
			throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
240 1
		});
241
242
		try {
243
			// Start output buffer to capture any output
244 1
			ob_start();
245
246
			// Start profiler
247 1
			self::start($key);
248
249
			// Execute callback, and get result
250 1
			$callbackResult = call_user_func_array($callback, $callbackParams);
251
252
			// Stop profiler
253 1
			self::stop($key);
254
255
			// Get and clean output buffer
256 1
			$callbackOutput = ob_get_clean();
257 1
		} catch (\ErrorException $callbackException) {
258
			// Stop and clean output buffer
259 1
			ob_end_clean();
260
261
			// Show error message
262 1
			Debugger::output('Invalid callback sent to Timer::callback: ' . str_replace('callback: ', '', $key));
263
264
			// Clear the item from the collection
265 1
			unset(self::$collection[$key]);
266
267
			// Clear callback result and output
268 1
			unset($callbackResult, $callbackOutput);
269
270
			// Set return value to false
271 1
			$returnValue = false;
272
		}
273
274
		// Restore error handler
275 1
		restore_error_handler();
276
277
		// Return result, output or true
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
278 1
		return (isset($callbackResult) ? $callbackResult : (!empty($callbackOutput) ? $callbackOutput : $returnValue));
279
	}
280
281
	/**
282
	 * @param string|null $key
283
	 * @param array       $options
284
	 *
285
	 * @codeCoverageIgnore
286
	 */
287
	public static function show($key = null, $options = []) {
288
		$output = self::getStats($key, $options);
289
290
		if (!empty($output)) {
291
			Debugger::output($output);
292
		}
293
	}
294
295
	/**
296
	 * @param array $options
297
	 *
298
	 * @codeCoverageIgnore
299
	 */
300
	public static function showAll($options = []) {
301
		// Stop started items
302
		if (count(self::$runningItems)) {
303
			foreach (self::$runningItems as $key => $value) {
304
				self::stop($key);
305
			}
306
		}
307
308
		// Output items
309
		$output    = '';
310
		$itemCount = 1;
311
		foreach (self::$collection as $key => $item) {
312
			$stats = self::getStats($key, $options);
313
314
			if (php_sapi_name() == 'cli') {
315
				$output .= (!empty($output) ? "\n" : '') . $stats;
316
			} else {
317
				$output .= '<div class="xicrow-php-debug-timer">';
318
				$output .= $stats;
319
				$output .= '</div>';
320
			}
321
322
			$itemCount++;
323
324
			unset($stats);
325
		}
326
		unset($itemCount);
327
328
		$output .= '<style type="text/css">';
329
		$output .= 'pre.xicrow-php-debug-debugger div.xicrow-php-debug-timer{';
330
		$output .= 'cursor: pointer;';
331
		$output .= '}';
332
		$output .= 'pre.xicrow-php-debug-debugger div.xicrow-php-debug-timer:hover{';
333
		$output .= 'font-weight: bold;';
334
		$output .= 'background-color: #EEE;';
335
		$output .= '}';
336
		$output .= '</style>';
337
338
		Debugger::output($output);
339
	}
340
341
	/**
342
	 * @param string|null $key
343
	 * @param array       $options
344
	 *
345
	 * @return string
346
	 */
347 1
	public static function getStats($key, $options = []) {
348
		// Merge options with default options
349 1
		$options = array_merge([
350
			// Show nested (boolean)
351 1
			'nested'         => true,
352
			// Prefix for nested items (string)
353 1
			'nested_prefix'  => '|-- ',
354
			// Max key length (int)
355
			'max_key_length' => 100
356 1
		], $options);
357
358
		// If item does not exist
359 1
		if (!isset(self::$collection[$key])) {
360 1
			return 'Unknow item in with key: ' . $key;
361
		}
362
363
		// Get item
364 1
		$item = self::$collection[$key];
365
366
		// Get item result
367 1
		$itemResult          = 'N/A';
368 1
		$itemResultFormatted = 'N/A';
369 1
		if (isset($item['start']) && isset($item['stop'])) {
370 1
			$itemResult          = (($item['stop'] - $item['start']) * 1000);
371 1
			$itemResultFormatted = self::formatMiliseconds($itemResult, 4, self::$forceDisplayUnit);
372 1
		}
373
374
		// Variable for output
375 1
		$output = '';
376
377
		// Prep key for output
378 1
		$outputName = '';
379 1
		$outputName .= ($options['nested'] ? str_repeat($options['nested_prefix'], $item['level']) : '');
380 1
		$outputName .= $item['key'];
381 1
		if (strlen($outputName) > $options['max_key_length']) {
382 1
			$outputName = '~' . substr($item['key'], -($options['max_key_length'] - 1));
383 1
		}
384
385
		// Add item stats
386 1
		$output .= str_pad($outputName, $options['max_key_length'], ' ');
387 1
		$output .= ' | ';
388 1
		$output .= str_pad($itemResultFormatted, 20, ' ', ($itemResult == 'N/A' ? STR_PAD_RIGHT : STR_PAD_LEFT));
389
390 1
		if (php_sapi_name() != 'cli' && is_array(self::$colorThreshold) && count(self::$colorThreshold)) {
391
			krsort(self::$colorThreshold);
392
			foreach (self::$colorThreshold as $value => $color) {
393
				if (is_numeric($itemResult) && $itemResult >= $value) {
394
					$output = '<span style="color: ' . $color . ';">' . $output . '</span>';
395
				}
396
			}
397
		}
398
399 1
		return $output;
400
	}
401
402
	/**
403
	 * @param int|float   $number
404
	 * @param int         $precision
405
	 * @param null|string $forceUnit
406
	 *
407
	 * @return string
408
	 */
409 1
	public static function formatMiliseconds($number = 0, $precision = 2, $forceUnit = null) {
410
		$units = [
411 1
			'MS' => 1,
412 1
			'S'  => 1000,
413 1
			'M'  => 60,
414 1
			'H'  => 60,
415 1
			'D'  => 24,
416
			'W'  => 7
417 1
		];
418
419 1
		if (is_null($forceUnit)) {
420 1
			$forceUnit = self::$forceDisplayUnit;
421 1
		}
422
423 1
		$value = $number;
424 1
		if (!empty($forceUnit) && array_key_exists($forceUnit, $units)) {
425 1
			$unit = $forceUnit;
426 1
			foreach ($units as $k => $v) {
427 1
				$value = ($value / $v);
428 1
				if ($k == $unit) {
429 1
					break;
430
				}
431 1
			}
432 1
		} else {
433 1
			$unit = '';
434 1
			foreach ($units as $k => $v) {
435 1
				if (empty($unit) || ($value / $v) > 1) {
436 1
					$value = ($value / $v);
437 1
					$unit  = $k;
438 1
				} else {
439 1
					break;
440
				}
441 1
			}
442
		}
443
444 1
		return sprintf('%0.' . $precision . 'f', $value) . ' ' . str_pad($unit, 2, ' ', STR_PAD_RIGHT);
445
	}
446
}
447