Timer::custom()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 8
cts 8
cp 1
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
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
     */
43
    public static $output = true;
44
45
    /**
46
     * @var array
47
     */
48
    public static $style = [
49
        'output_format'      => '<pre style="margin-top: 0; padding: 5px; font-family: Menlo, Monaco, Consolas, monospace; font-weight: bold; font-size: 12px; background-color: #18171B; border: none; color: #FF8400; display: block; z-index: 1000;">%s</pre>',
50
        'called_from_format' => '<pre style="margin-bottom: 0; padding: 5px; font-family: Menlo, Monaco, Consolas, monospace; font-weight: normal; font-size: 12px; background-color: #18171B; border: none; color: #AAAAAA; display: block; z-index: 1000;">%s</pre>',
51
        'hover_style'        => 'background-color: #333333;',
52
    ];
53
54
    /**
55
     * @param string $data
56
     *
57
     * @codeCoverageIgnore
58
     */
59
    public static function output($data)
60
    {
61
        if (!self::$output || !is_string($data)) {
62
            return;
63
        }
64
65
        if (php_sapi_name() != 'cli') {
66
            echo sprintf(self::$style['called_from_format'], Debugger::getCalledFrom(2));
67
            echo sprintf(self::$style['output_format'], $data);
68
            echo '<style type="text/css">.xicrow-php-debug-timer:hover{ ' . self::$style['hover_style'] . ' }</style>';
69
        } else {
70
            echo Debugger::getCalledFrom(2);
71
            echo "\n";
72
            echo $data;
73
        }
74
    }
75
76
    /**
77
     *
78
     */
79 6
    public static function reset()
80
    {
81 6
        self::$collection   = [];
82 6
        self::$currentItem  = false;
83 6
        self::$runningItems = [];
84 6
    }
85
86
    /**
87
     * @param string|null $key
88
     * @param array       $data
89
     *
90
     * @return string
91
     */
92 6
    public static function add($key = null, $data = [])
93
    {
94
        // If no key is given
95 6
        if (is_null($key)) {
96
            // Set key to file and line
97
            $key = Debugger::getCalledFrom(2);
98
        }
99
100
        // If key is allready in use
101 6
        if (isset(self::$collection[$key])) {
102
            // Get original item
103 1
            $item = self::$collection[$key];
104
105
            // Set new item count
106 1
            $itemCount = (isset($item['count']) ? ($item['count'] + 1) : 2);
107
108
            // Set correct key for the original item
109 1
            if (strpos($item['key'], '#') === false) {
110 1
                self::$collection[$key] = array_merge($item, [
111 1
                    'key'   => $key . ' #1',
112 1
                    'count' => $itemCount,
113
                ]);
114
            } else {
115
                self::$collection[$key] = array_merge($item, [
116
                    'count' => $itemCount,
117
                ]);
118
            }
119
120
            // Set new key
121 1
            $key = $key . ' #' . $itemCount;
122
        }
123
124
        // Make sure various options are set
125 6
        if (!isset($data['key'])) {
126 6
            $data['key'] = $key;
127
        }
128 6
        if (!isset($data['parent'])) {
129 6
            $data['parent'] = self::$currentItem;
130
        }
131 6
        if (!isset($data['level'])) {
132 6
            $data['level'] = 0;
133 6
            if (isset($data['parent']) && isset(self::$collection[$data['parent']])) {
134 1
                $data['level'] = (self::$collection[$data['parent']]['level'] + 1);
135
            }
136
        }
137
138
        // Add item to collection
139 6
        self::$collection[$key] = $data;
140
141 6
        return $key;
142
    }
143
144
    /**
145
     * @param string|null $key
146
     *
147
     * @return string
148
     */
149 3
    public static function start($key = null)
150
    {
151
        // Add new item
152 3
        $key = self::add($key, [
153 3
            'start' => microtime(true),
154
        ]);
155
156
        // Set current item
157 3
        self::$currentItem = $key;
158
159
        // Add to running items
160 3
        self::$runningItems[$key] = true;
161
162 3
        return $key;
163
    }
164
165
    /**
166
     * @param string|null $key
167
     *
168
     * @return string
169
     */
170 2
    public static function stop($key = null)
171
    {
172
        // If no key is given
173 2
        if (is_null($key)) {
174
            // Get key of the last started item
175 1
            end(self::$runningItems);
176 1
            $key = key(self::$runningItems);
177
        }
178
179
        // Check for key duplicates, and find the last one not stopped
180 2
        if (isset(self::$collection[$key]) && isset(self::$collection[$key . ' #2'])) {
181 1
            $lastNotStopped = false;
182 1
            $currentKey     = $key;
183 1
            $currentIndex   = 1;
184 1
            while (isset(self::$collection[$currentKey])) {
185 1
                if (!isset(self::$collection[$currentKey]['stop'])) {
186 1
                    $lastNotStopped = $currentKey;
187
                }
188
189 1
                $currentIndex++;
190 1
                $currentKey = $key . ' #' . $currentIndex;
191
            }
192
193 1
            if ($lastNotStopped) {
194 1
                $key = $lastNotStopped;
195
            }
196
        }
197
198
        // If item exists in collection
199 2
        if (isset(self::$collection[$key])) {
200
            // Update the item
201 2
            self::$collection[$key]['stop'] = microtime(true);
202
203 2
            self::$currentItem = self::$collection[$key]['parent'];
204
        }
205
206 2
        if (isset(self::$runningItems[$key])) {
207 2
            unset(self::$runningItems[$key]);
208
        }
209
210 2
        return $key;
211
    }
212
213
    /**
214
     * @param string|null    $key
215
     * @param int|float|null $start
216
     * @param int|float|null $stop
217
     *
218
     * @return string
219
     */
220 2
    public static function custom($key = null, $start = null, $stop = null)
221
    {
222
        // Add new item
223 2
        self::add($key, [
224 2
            'start' => $start,
225 2
            'stop'  => $stop,
226
        ]);
227
228
        // If no stop value is given
229 2
        if (is_null($stop)) {
230
            // Set current item
231 1
            self::$currentItem = $key;
232
233
            // Add to running items
234 1
            self::$runningItems[$key] = true;
235
        }
236
237 2
        return $key;
238
    }
239
240
    /**
241
     * @param string|null           $key
242
     * @param string|array|\Closure $callback
243
     *
244
     * @return mixed
245
     */
246 1
    public static function callback($key = null, $callback)
247
    {
248
        // Get parameters for callback
249 1
        $callbackParams = func_get_args();
250 1
        unset($callbackParams[0], $callbackParams[1]);
251 1
        $callbackParams = array_values($callbackParams);
252
253
        // Get key if no key is given
254 1
        if (is_null($key)) {
255 1
            if (is_string($callback)) {
256 1
                $key = $callback;
257 1
            } elseif (is_array($callback)) {
258 1
                $keyArr = [];
259 1
                foreach ($callback as $k => $v) {
260 1
                    if (is_string($v)) {
261 1
                        $keyArr[] = $v;
262 1
                    } elseif (is_object($v)) {
263 1
                        $keyArr[] = get_class($v);
264
                    }
265
                }
266
267 1
                $key = implode('', $keyArr);
268 1
                if (count($keyArr) > 1) {
269 1
                    $method = array_pop($keyArr);
270 1
                    $key    = implode('/', $keyArr);
271 1
                    $key    .= '::' . $method;
272
                }
273
274 1
                unset($keyArr, $method);
275 1
            } elseif (is_object($callback) && $callback instanceof \Closure) {
276 1
                $key = 'closure';
277
            }
278 1
            $key = 'callback: ' . $key;
279
        }
280
281
        // Set default return value
282 1
        $returnValue = true;
283
284
        // Set error handler, to convert errors to exceptions
285 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...
286 1
            throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
287 1
        });
288
289
        try {
290
            // Start output buffer to capture any output
291 1
            ob_start();
292
293
            // Start profiler
294 1
            self::start($key);
295
296
            // Execute callback, and get result
297 1
            $callbackResult = call_user_func_array($callback, $callbackParams);
298
299
            // Stop profiler
300 1
            self::stop($key);
301
302
            // Get and clean output buffer
303 1
            $callbackOutput = ob_get_clean();
304 1
        } catch (\ErrorException $callbackException) {
0 ignored issues
show
Bug introduced by
The class ErrorException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
305
            // Stop and clean output buffer
306 1
            ob_end_clean();
307
308
            // Show error message
309 1
            self::output('Invalid callback sent to Timer::callback: ' . str_replace('callback: ', '', $key));
310
311
            // Clear the item from the collection
312 1
            unset(self::$collection[$key]);
313
314
            // Clear callback result and output
315 1
            unset($callbackResult, $callbackOutput);
316
317
            // Set return value to false
318 1
            $returnValue = false;
319
        }
320
321
        // Restore error handler
322 1
        restore_error_handler();
323
324
        // Return result, output or true
325 1
        return (isset($callbackResult) ? $callbackResult : (!empty($callbackOutput) ? $callbackOutput : $returnValue));
326
    }
327
328
    /**
329
     * @param string|null $key
330
     * @param array       $options
331
     *
332
     * @codeCoverageIgnore
333
     */
334
    public static function show($key = null, $options = [])
335
    {
336
        $output = self::getStats($key, $options);
337
338
        if (!empty($output)) {
339
            self::output($output);
340
        }
341
    }
342
343
    /**
344
     * @param array $options
345
     *
346
     * @codeCoverageIgnore
347
     */
348
    public static function showAll($options = [])
349
    {
350
        // Stop started items
351
        if (count(self::$runningItems)) {
352
            foreach (self::$runningItems as $key => $value) {
353
                self::stop($key);
354
            }
355
        }
356
357
        // Output items
358
        $output    = '';
359
        $itemCount = 1;
360
        foreach (self::$collection as $key => $item) {
361
            $stats = self::getStats($key, $options);
362
363
            if (php_sapi_name() == 'cli') {
364
                $output .= (!empty($output) ? "\n" : '') . $stats;
365
            } else {
366
                $output .= '<div class="xicrow-php-debug-timer">';
367
                $output .= $stats;
368
                $output .= '</div>';
369
            }
370
371
            $itemCount++;
372
373
            unset($stats);
374
        }
375
        unset($itemCount);
376
377
        self::output($output);
378
    }
379
380
    /**
381
     * @param string|null $key
382
     * @param array       $options
383
     *
384
     * @return string
385
     */
386 1
    public static function getStats($key, $options = [])
387
    {
388
        // Merge options with default options
389 1
        $options = array_merge([
390
            // Show nested (boolean)
391 1
            'nested'         => true,
392
            // Prefix for nested items (string)
393
            'nested_prefix'  => '|-- ',
394
            // Max key length (int)
395
            'max_key_length' => 100,
396 1
        ], $options);
397
398
        // If item does not exist
399 1
        if (!isset(self::$collection[$key])) {
400 1
            return 'Unknow item in with key: ' . $key;
401
        }
402
403
        // Get item
404 1
        $item = self::$collection[$key];
405
406
        // Get item result
407 1
        $itemResult          = 'N/A';
408 1
        $itemResultFormatted = 'N/A';
409 1
        if (isset($item['start']) && isset($item['stop'])) {
410 1
            $itemResult          = (($item['stop'] - $item['start']) * 1000);
411 1
            $itemResultFormatted = self::formatMiliseconds($itemResult, 4, self::$forceDisplayUnit);
412
        }
413
414
        // Variable for output
415 1
        $output = '';
416
417
        // Prep key for output
418 1
        $outputName = '';
419 1
        $outputName .= ($options['nested'] ? str_repeat($options['nested_prefix'], $item['level']) : '');
420 1
        $outputName .= $item['key'];
421 1
        if (mb_strlen($outputName) > $options['max_key_length']) {
422 1
            $outputName = '~' . mb_substr($item['key'], -($options['max_key_length'] - 1));
423
        }
424
425
        // Add item stats
426 1
        $output .= str_pad($outputName, ($options['max_key_length'] + (strlen($outputName) - mb_strlen($outputName))), ' ');
427 1
        $output .= ' | ';
428 1
        $output .= str_pad($itemResultFormatted, 20, ' ', ($itemResult == 'N/A' ? STR_PAD_RIGHT : STR_PAD_LEFT));
429
430 1
        if (php_sapi_name() != 'cli' && is_array(self::$colorThreshold) && count(self::$colorThreshold)) {
431
            krsort(self::$colorThreshold);
432
            foreach (self::$colorThreshold as $value => $color) {
433
                if (is_numeric($itemResult) && $itemResult >= $value) {
434
                    $output = '<span style="color: ' . $color . ';">' . $output . '</span>';
435
                }
436
            }
437
        }
438
439 1
        return $output;
440
    }
441
442
    /**
443
     * @param int|float   $number
444
     * @param int         $precision
445
     * @param null|string $forceUnit
446
     *
447
     * @return string
448
     */
449 1
    public static function formatMiliseconds($number = 0, $precision = 2, $forceUnit = null)
450
    {
451
        $units = [
452 1
            'MS' => 1,
453
            'S'  => 1000,
454
            'M'  => 60,
455
            'H'  => 60,
456
            'D'  => 24,
457
            'W'  => 7,
458
        ];
459
460 1
        if (is_null($forceUnit)) {
461 1
            $forceUnit = self::$forceDisplayUnit;
462
        }
463
464 1
        $value = $number;
465 1
        if (!empty($forceUnit) && array_key_exists($forceUnit, $units)) {
466 1
            $unit = $forceUnit;
467 1
            foreach ($units as $k => $v) {
468 1
                $value = ($value / $v);
469 1
                if ($k == $unit) {
470 1
                    break;
471
                }
472
            }
473
        } else {
474 1
            $unit = '';
475 1
            foreach ($units as $k => $v) {
476 1
                if (empty($unit) || ($value / $v) > 1) {
477 1
                    $value = ($value / $v);
478 1
                    $unit  = $k;
479
                } else {
480 1
                    break;
481
                }
482
            }
483
        }
484
485 1
        return sprintf('%0.' . $precision . 'f', $value) . ' ' . str_pad($unit, 2, ' ', STR_PAD_RIGHT);
486
    }
487
}
488