Passed
Push — master ( 6e50e5...7e0eb4 )
by Jan
03:10
created

Timer::add()   C

Complexity

Conditions 10
Paths 120

Size

Total Lines 51
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10.3811

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 51
ccs 27
cts 32
cp 0.8438
rs 5.6296
cc 10
eloc 24
nc 120
nop 2
crap 10.3811

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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