Test Setup Failed
Push — master ( 0d8b13...6e50e5 )
by Jan
04:44
created

Timer::output()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.3731

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
ccs 5
cts 7
cp 0.7143
rs 9.2
cc 4
eloc 15
nc 3
nop 1
crap 4.3731
1
<?php
2
namespace Xicrow\PhpDebug;
3
4
/**
5
 * Class Timer
6
 *
7
 * @package Xicrow\PhpDebug
8
 */
9
class Timer
10
{
11
    /**
12
     * @var array
13
     */
14
    public static $collection = [];
15
16
    /**
17
     * @var boolean|string
18
     */
19
    public static $currentItem = false;
20
21
    /**
22
     * @var array
23
     */
24
    public static $runningItems = [];
25
26
    /**
27
     * Force the unit to display elapsed times in (MS|S|M|H|D|W)
28
     *
29
     * @var null|string
30
     */
31
    public static $forceDisplayUnit = null;
32
33
    /**
34
     * Color threshold for output (5 => 'red', all items with values of 5 or higher will be red)
35
     *
36
     * @var array
37
     */
38
    public static $colorThreshold = [];
39
40
    /**
41
     * @var bool
42 6
     */
43 6
    public static $output = true;
44 6
45 6
    /**
46 6
     * @param string $data
47
     *
48
     * @codeCoverageIgnore
49
     */
50
    public static function output($data)
51
    {
52
        if (!self::$output || !is_string($data)) {
53
            return;
54 6
        }
55
56 6
        if (php_sapi_name() != 'cli') {
57
            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;">';
58
            echo Debugger::getCalledFrom(2);
59
            echo '</pre>';
60
            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;">';
61
            echo $data;
62 6
            echo '</pre>';
63
            echo '<style type="text/css">.xicrow-php-debug-timer:hover{ background-color: #333333; }</style>';
64 1
        } else {
65
            echo Debugger::getCalledFrom(2);
66
            echo "\n";
67 1
            echo $data;
68
        }
69
    }
70 1
71 1
    /**
72 1
     *
73 1
     */
74 1
    public static function reset()
75 1
    {
76
        self::$collection   = [];
77
        self::$currentItem  = false;
78
        self::$runningItems = [];
79
    }
80
81
    /**
82 1
     * @param string|null $key
83 1
     * @param array       $data
84
     *
85
     * @return string
86 6
     */
87 6
    public static function add($key = null, $data = [])
88 6
    {
89 6
        // If no key is given
90 6
        if (is_null($key)) {
91 6
            // Set key to file and line
92 6
            $key = Debugger::getCalledFrom(2);
93 6
        }
94 6
95 1
        // If key is allready in use
96 1
        if (isset(self::$collection[$key])) {
97 6
            // Get original item
98
            $item = self::$collection[$key];
99
100 6
            // Set new item count
101
            $itemCount = (isset($item['count']) ? ($item['count'] + 1) : 2);
102 6
103
            // Set correct key for the original item
104
            if (strpos($item['key'], '#') === false) {
105
                self::$collection[$key] = array_merge($item, [
106
                    'key'   => $key . ' #1',
107
                    'count' => $itemCount,
108
                ]);
109
            } else {
110 3
                self::$collection[$key] = array_merge($item, [
111
                    'count' => $itemCount,
112 3
                ]);
113 3
            }
114 3
115
            // Set new key
116
            $key = $key . ' #' . $itemCount;
117 3
        }
118
119
        // Make sure various options are set
120 3
        if (!isset($data['key'])) {
121
            $data['key'] = $key;
122 3
        }
123
        if (!isset($data['parent'])) {
124
            $data['parent'] = self::$currentItem;
125
        }
126
        if (!isset($data['level'])) {
127
            $data['level'] = 0;
128
            if (isset($data['parent']) && isset(self::$collection[$data['parent']])) {
129
                $data['level'] = (self::$collection[$data['parent']]['level'] + 1);
130 2
            }
131
        }
132 2
133
        // Add item to collection
134 1
        self::$collection[$key] = $data;
135 1
136 1
        return $key;
137
    }
138
139 2
    /**
140 1
     * @param string|null $key
141 1
     *
142 1
     * @return string
143 1
     */
144 1
    public static function start($key = null)
145 1
    {
146 1
        // Add new item
147
        $key = self::add($key, [
148 1
            'start' => microtime(true),
149 1
        ]);
150 1
151
        // Set current item
152 1
        self::$currentItem = $key;
153 1
154 1
        // Add to running items
155 1
        self::$runningItems[$key] = true;
156
157
        return $key;
158 2
    }
159
160 2
    /**
161
     * @param string|null $key
162 2
     *
163 2
     * @return string
164
     */
165 2
    public static function stop($key = null)
166 2
    {
167 2
        // If no key is given
168
        if (is_null($key)) {
169 2
            // Get key of the last started item
170
            end(self::$runningItems);
171
            $key = key(self::$runningItems);
172
        }
173
174
        // Check for key duplicates, and find the last one not stopped
175
        if (isset(self::$collection[$key]) && isset(self::$collection[$key . ' #2'])) {
176
            $lastNotStopped = false;
177
            $currentKey     = $key;
178
            $currentIndex   = 1;
179 2
            while (isset(self::$collection[$currentKey])) {
180
                if (!isset(self::$collection[$currentKey]['stop'])) {
181 2
                    $lastNotStopped = $currentKey;
182 2
                }
183 2
184 2
                $currentIndex++;
185
                $currentKey = $key . ' #' . $currentIndex;
186
            }
187 2
188
            if ($lastNotStopped) {
189 1
                $key = $lastNotStopped;
190
            }
191
        }
192 1
193 1
        // If item exists in collection
194
        if (isset(self::$collection[$key])) {
195 2
            // Update the item
196
            self::$collection[$key]['stop'] = microtime(true);
197
198
            self::$currentItem = self::$collection[$key]['parent'];
199
        }
200
201
        if (isset(self::$runningItems[$key])) {
202
            unset(self::$runningItems[$key]);
203
        }
204 1
205
        return $key;
206 1
    }
207 1
208 1
    /**
209
     * @param string|null    $key
210
     * @param int|float|null $start
211 1
     * @param int|float|null $stop
212 1
     *
213 1
     * @return string
214 1
     */
215 1
    public static function custom($key = null, $start = null, $stop = null)
216 1
    {
217 1
        // Add new item
218 1
        self::add($key, [
219 1
            'start' => $start,
220 1
            'stop'  => $stop,
221 1
        ]);
222 1
223
        // If no stop value is given
224 1
        if (is_null($stop)) {
225 1
            // Set current item
226 1
            self::$currentItem = $key;
227 1
228 1
            // Add to running items
229 1
            self::$runningItems[$key] = true;
230
        }
231 1
232 1
        return $key;
233 1
    }
234 1
235 1
    /**
236 1
     * @param string|null           $key
237
     * @param string|array|\Closure $callback
238
     *
239 1
     * @return mixed
240
     */
241
    public static function callback($key = null, $callback)
242 1
    {
243 1
        // Get parameters for callback
244 1
        $callbackParams = func_get_args();
245
        unset($callbackParams[0], $callbackParams[1]);
246
        $callbackParams = array_values($callbackParams);
247
248 1
        // Get key if no key is given
249
        if (is_null($key)) {
250
            if (is_string($callback)) {
251 1
                $key = $callback;
252
            } elseif (is_array($callback)) {
253
                $keyArr = [];
254 1
                foreach ($callback as $k => $v) {
255
                    if (is_string($v)) {
256
                        $keyArr[] = $v;
257 1
                    } elseif (is_object($v)) {
258
                        $keyArr[] = get_class($v);
259
                    }
260 1
                }
261 1
262
                $key = implode('', $keyArr);
263 1
                if (count($keyArr) > 1) {
264
                    $method = array_pop($keyArr);
265
                    $key    = implode('/', $keyArr);
266 1
                    $key    .= '::' . $method;
267
                }
268
269 1
                unset($keyArr, $method);
270
            } elseif (is_object($callback) && $callback instanceof \Closure) {
271
                $key = 'closure';
272 1
            }
273
            $key = 'callback: ' . $key;
274
        }
275 1
276
        // Set default return value
277
        $returnValue = true;
278
279 1
        // Set error handler, to convert errors to exceptions
280
        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...
281
            throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
282 1
        });
283
284
        try {
285
            // Start output buffer to capture any output
286
            ob_start();
287
288
            // Start profiler
289
            self::start($key);
290
291
            // Execute callback, and get result
292
            $callbackResult = call_user_func_array($callback, $callbackParams);
293
294
            // Stop profiler
295
            self::stop($key);
296
297
            // Get and clean output buffer
298
            $callbackOutput = ob_get_clean();
299
        } catch (\ErrorException $callbackException) {
300
            // Stop and clean output buffer
301
            ob_end_clean();
302
303
            // Show error message
304
            self::output('Invalid callback sent to Timer::callback: ' . str_replace('callback: ', '', $key));
305
306
            // Clear the item from the collection
307
            unset(self::$collection[$key]);
308
309
            // Clear callback result and output
310
            unset($callbackResult, $callbackOutput);
311
312
            // Set return value to false
313
            $returnValue = false;
314
        }
315
316
        // Restore error handler
317
        restore_error_handler();
318
319
        // Return result, output or true
320
        return (isset($callbackResult) ? $callbackResult : (!empty($callbackOutput) ? $callbackOutput : $returnValue));
321
    }
322
323
    /**
324
     * @param string|null $key
325
     * @param array       $options
326
     *
327
     * @codeCoverageIgnore
328
     */
329
    public static function show($key = null, $options = [])
330
    {
331
        $output = self::getStats($key, $options);
332
333
        if (!empty($output)) {
334
            self::output($output);
335
        }
336
    }
337
338
    /**
339
     * @param array $options
340
     *
341
     * @codeCoverageIgnore
342
     */
343
    public static function showAll($options = [])
344
    {
345
        // Stop started items
346
        if (count(self::$runningItems)) {
347
            foreach (self::$runningItems as $key => $value) {
348
                self::stop($key);
349
            }
350
        }
351
352
        // Output items
353 1
        $output    = '';
354
        $itemCount = 1;
355 1
        foreach (self::$collection as $key => $item) {
356
            $stats = self::getStats($key, $options);
357 1
358
            if (php_sapi_name() == 'cli') {
359 1
                $output .= (!empty($output) ? "\n" : '') . $stats;
360
            } else {
361 1
                $output .= '<div class="xicrow-php-debug-timer">';
362 1
                $output .= $stats;
363
                $output .= '</div>';
364
            }
365 1
366 1
            $itemCount++;
367
368
            unset($stats);
369
        }
370 1
        unset($itemCount);
371
372
        self::output($output);
373 1
    }
374 1
375 1
    /**
376 1
     * @param string|null $key
377 1
     * @param array       $options
378 1
     *
379
     * @return string
380
     */
381 1
    public static function getStats($key, $options = [])
382
    {
383
        // Merge options with default options
384 1
        $options = array_merge([
385 1
            // Show nested (boolean)
386 1
            'nested'         => true,
387 1
            // Prefix for nested items (string)
388 1
            'nested_prefix'  => '|-- ',
389 1
            // Max key length (int)
390
            'max_key_length' => 100,
391
        ], $options);
392 1
393 1
        // If item does not exist
394 1
        if (!isset(self::$collection[$key])) {
395
            return 'Unknow item in with key: ' . $key;
396 1
        }
397
398
        // Get item
399
        $item = self::$collection[$key];
400
401
        // Get item result
402
        $itemResult          = 'N/A';
403
        $itemResultFormatted = 'N/A';
404
        if (isset($item['start']) && isset($item['stop'])) {
405 1
            $itemResult          = (($item['stop'] - $item['start']) * 1000);
406
            $itemResultFormatted = self::formatMiliseconds($itemResult, 4, self::$forceDisplayUnit);
407
        }
408
409
        // Variable for output
410
        $output = '';
411
412
        // Prep key for output
413
        $outputName = '';
414
        $outputName .= ($options['nested'] ? str_repeat($options['nested_prefix'], $item['level']) : '');
415 1
        $outputName .= $item['key'];
416
        if (mb_strlen($outputName) > $options['max_key_length']) {
417 1
            $outputName = '~' . mb_substr($item['key'], -($options['max_key_length'] - 1));
418 1
        }
419 1
420 1
        // Add item stats
421 1
        $output .= str_pad($outputName, ($options['max_key_length'] + (strlen($outputName) - mb_strlen($outputName))), ' ');
422 1
        $output .= ' | ';
423 1
        $output .= str_pad($itemResultFormatted, 20, ' ', ($itemResult == 'N/A' ? STR_PAD_RIGHT : STR_PAD_LEFT));
424
425 1
        if (php_sapi_name() != 'cli' && is_array(self::$colorThreshold) && count(self::$colorThreshold)) {
426 1
            krsort(self::$colorThreshold);
427 1
            foreach (self::$colorThreshold as $value => $color) {
428
                if (is_numeric($itemResult) && $itemResult >= $value) {
429 1
                    $output = '<span style="color: ' . $color . ';">' . $output . '</span>';
430 1
                }
431 1
            }
432 1
        }
433 1
434 1
        return $output;
435 1
    }
436
437 1
    /**
438 1
     * @param int|float   $number
439 1
     * @param int         $precision
440 1
     * @param null|string $forceUnit
441 1
     *
442 1
     * @return string
443 1
     */
444 1
    public static function formatMiliseconds($number = 0, $precision = 2, $forceUnit = null)
445 1
    {
446
        $units = [
447 1
            'MS' => 1,
448
            'S'  => 1000,
449
            'M'  => 60,
450 1
            'H'  => 60,
451
            'D'  => 24,
452
            'W'  => 7,
453
        ];
454
455
        if (is_null($forceUnit)) {
456
            $forceUnit = self::$forceDisplayUnit;
457
        }
458
459
        $value = $number;
460
        if (!empty($forceUnit) && array_key_exists($forceUnit, $units)) {
461
            $unit = $forceUnit;
462
            foreach ($units as $k => $v) {
463
                $value = ($value / $v);
464
                if ($k == $unit) {
465
                    break;
466
                }
467
            }
468
        } else {
469
            $unit = '';
470
            foreach ($units as $k => $v) {
471
                if (empty($unit) || ($value / $v) > 1) {
472
                    $value = ($value / $v);
473
                    $unit  = $k;
474
                } else {
475
                    break;
476
                }
477
            }
478
        }
479
480
        return sprintf('%0.' . $precision . 'f', $value) . ' ' . str_pad($unit, 2, ' ', STR_PAD_RIGHT);
481
    }
482
}
483