Complex classes like Timer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Timer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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) { |
|
|
|||
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 = []) |
||
337 | |||
338 | /** |
||
339 | * @param array $options |
||
340 | * |
||
341 | * @codeCoverageIgnore |
||
342 | */ |
||
343 | public static function showAll($options = []) |
||
344 | { |
||
374 | |||
375 | /** |
||
376 | * @param string|null $key |
||
377 | * @param array $options |
||
378 | * |
||
379 | * @return string |
||
380 | */ |
||
381 | 1 | public static function getStats($key, $options = []) |
|
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) |
|
482 | } |
||
483 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.