Total Complexity | 57 |
Total Lines | 492 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like ExceptionHandler 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.
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 ExceptionHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
35 | class ExceptionHandler |
||
36 | { |
||
37 | /** |
||
38 | * Gets caught buffer of memory. |
||
39 | * |
||
40 | * @var mixed $caughtBuffer |
||
41 | */ |
||
42 | protected $caughtBuffer; |
||
43 | |||
44 | /** |
||
45 | * Gets caught lenght of buffer. |
||
46 | * |
||
47 | * @var int $caughtLength |
||
48 | */ |
||
49 | protected $caughtLength; |
||
50 | |||
51 | /** |
||
52 | * Gets the charset. By default UTF-8. |
||
53 | * |
||
54 | * @var string $charset |
||
55 | */ |
||
56 | protected $charset; |
||
57 | |||
58 | /** |
||
59 | * Gets activation of debugging. |
||
60 | * |
||
61 | * @var bool $debug |
||
62 | */ |
||
63 | protected $debug; |
||
64 | |||
65 | /** |
||
66 | * Gets the file link format. |
||
67 | * |
||
68 | * @var string $fileLinkFormat |
||
69 | */ |
||
70 | protected $fileLinkFormat; |
||
71 | |||
72 | /** |
||
73 | * Gets an error handler. |
||
74 | * |
||
75 | * @var string $handler |
||
76 | */ |
||
77 | protected $handler; |
||
78 | |||
79 | /** |
||
80 | * Register the exception handler. |
||
81 | * |
||
82 | * @param bool $debug |
||
83 | * @param string|null $charset |
||
84 | * @param string|null $fileLinkformat |
||
85 | * |
||
86 | * @return static |
||
87 | */ |
||
88 | public static function register($debug = true, $charset = null, $fileLinkFormat = null) |
||
89 | { |
||
90 | $handler = new static($debug, $charset, $fileLinkFormat); |
||
91 | |||
92 | set_exception_handler([$handler, 'handle']); |
||
93 | |||
94 | return $handler; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Constructor. Initialize the ExceptionHandler instance. |
||
99 | * |
||
100 | * @param bool $debug |
||
101 | * @param string|null $charset |
||
102 | * @param string|null $fileLinkformat |
||
103 | * |
||
104 | * @return void |
||
105 | */ |
||
106 | public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null) |
||
107 | { |
||
108 | $this->debug = $debug; |
||
109 | $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; |
||
110 | $this->fileLinkFormat = $fileLinkFormat; |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Sets a user exception handler. |
||
115 | * |
||
116 | * @param \Callable $handler |
||
|
|||
117 | * |
||
118 | * @return \Callable|null |
||
119 | */ |
||
120 | public function setHandler(Callable $handler) |
||
121 | { |
||
122 | $oldHandler = $this->handler; |
||
123 | $this->handler = $handler; |
||
124 | |||
125 | return $oldHandler; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Sets the format for links to source files. |
||
130 | * |
||
131 | * @param string|FileLinkFormatter $fileLinkFormat |
||
132 | * |
||
133 | * @return string |
||
134 | */ |
||
135 | public function setFileLinkFormat($fileLinkFormat) |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Sends a response for the given Exception. |
||
145 | * |
||
146 | * How does it work: |
||
147 | * First, the exception is handled by system exception handler, then by the user exception handler. |
||
148 | * The latter has priority and any exit from the first one is canceled. |
||
149 | * |
||
150 | * @param \Exception $exception |
||
151 | * |
||
152 | * @return void |
||
153 | */ |
||
154 | public function handler(Exception $exception) |
||
155 | { |
||
156 | if ($this->handler === null && $exception instanceof OutOfMemoryException) { |
||
157 | $this->sendPhpResponse($exception); |
||
158 | } |
||
159 | |||
160 | $caughtLength = $this->caughtLength = 0; |
||
161 | |||
162 | ob_start(function ($buffer) { |
||
163 | $this->caughtBuffer = $buffer; |
||
164 | |||
165 | return ''; |
||
166 | }); |
||
167 | |||
168 | $this->sendPhpResponse($exception); |
||
169 | |||
170 | if (isset($this->caughtBuffer[0])) { |
||
171 | ob_start(function ($buffer) { |
||
172 | if ($this->caughtLength) { |
||
173 | $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); |
||
174 | |||
175 | if (isset($cleanBuffer[0])) { |
||
176 | $buffer = $cleanBuffer; |
||
177 | } |
||
178 | } |
||
179 | |||
180 | return $buffer; |
||
181 | |||
182 | }); |
||
183 | |||
184 | echo $this->caughtBuffer; |
||
185 | // Return the length of the output buffer. |
||
186 | $caughtLength = ob_get_length(); |
||
187 | } |
||
188 | |||
189 | $this->caughtBuffer = null; |
||
190 | |||
191 | try { |
||
192 | ($this->handler)($exception); |
||
193 | $caughtLength = $this->caughtLength; |
||
194 | } catch (Exception $e) { |
||
195 | if ( ! $caughtLength) { |
||
196 | throw $e; |
||
197 | } |
||
198 | } |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Sends the error associated with the given Exception as a plain PHP response. |
||
203 | * |
||
204 | * This method uses plain php functions as header and echo to generate the output |
||
205 | * response. |
||
206 | * |
||
207 | * @param \Exception|\Syscodes\Debug\FlattenExceptions\FlattenException $exception An \Exception or \FlattenException instance |
||
208 | * |
||
209 | * @return string The HTML content as a string |
||
210 | */ |
||
211 | public function sendPhpResponse($exception) |
||
212 | { |
||
213 | if ( ! $exception instanceof FlattenException) { |
||
214 | $exception = FlattenException::make($exception); |
||
215 | } |
||
216 | |||
217 | if ( ! headers_sent()) { |
||
218 | header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); |
||
219 | |||
220 | foreach ($exception->getHeaders() as $name => $value) { |
||
221 | header($name.':'.$value, false); |
||
222 | } |
||
223 | |||
224 | header('Content-Type: text/html; charset='.$this->charset); |
||
225 | } |
||
226 | |||
227 | echo $this->design($this->getContent($exception), $this->getStylesheet()); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Gets the full HTML content associated with the given exception. |
||
232 | * |
||
233 | * @param \Exception|\Syscodes\Debug\FlattenExceptions\FlattenException $exception An \Exception or \FlattenException instance |
||
234 | * |
||
235 | * @return string The HTML content as a string |
||
236 | */ |
||
237 | public function getHtmlResponse($exception) |
||
238 | { |
||
239 | if ( ! $exception instanceof FlattenException) { |
||
240 | $exception = FlattenException::make($exception); |
||
241 | } |
||
242 | |||
243 | echo $this->design($this->getContent($exception), $this->getStylesheet()); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Layout HTML for gets the content and style css. |
||
248 | * |
||
249 | * @param string $content |
||
250 | * @param string $styleCss |
||
251 | * |
||
252 | * @return string |
||
253 | */ |
||
254 | private function design($content, $styleCss) |
||
269 | </body> |
||
270 | </html> |
||
271 | EOF; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Gets the HTML content associated with the given exception. |
||
276 | * |
||
277 | * @param \Syscodes\Debug\FlattenExceptions\FlattenException $exception |
||
278 | * |
||
279 | * @return string The HTML content as a string |
||
280 | */ |
||
281 | public function getContent(FlattenException $exception) |
||
282 | { |
||
283 | switch ($exception->getStatusCode()) { |
||
284 | case 404: |
||
285 | $title = 'Sorry, the page you are looking to could not be found'; |
||
286 | break; |
||
287 | default: |
||
288 | $title = 'Whoops, looks like something went wrong'; |
||
289 | } |
||
290 | |||
291 | if ( ! $this->debug) { |
||
292 | return <<<EOF |
||
293 | <div class="container"> |
||
294 | <h1>$title</h1> |
||
295 | </div> |
||
296 | EOF; |
||
297 | } |
||
298 | |||
299 | $content = ''; |
||
300 | |||
301 | try { |
||
302 | $count = count($exception->getAllPrevious()); |
||
303 | $total = $count + 1; |
||
304 | |||
305 | foreach ($exception->toArray() as $position => $e) { |
||
306 | $index = $count - $position + 1; |
||
307 | $class = $this->formatClass($e['class']); |
||
308 | $message = nl2br($this->escapeHtml($e['message'])); |
||
309 | $content .= sprintf(<<<'EOF' |
||
310 | <div class="trace"> |
||
311 | <table> |
||
312 | <tr class="trace-head"><th> |
||
313 | <h3 class="trace-class"> |
||
314 | <span class="text-muted">(%d/%d)</span> |
||
315 | <span class="exception_title">%s</span> |
||
316 | </h3> |
||
317 | <p class="break-long-words trace-message">%s</p> |
||
318 | </th></tr> |
||
319 | EOF |
||
320 | , $index, $total, $class, $message); |
||
321 | |||
322 | foreach ($e['trace'] as $trace) { |
||
323 | $content .= '<tr><td>'; |
||
324 | |||
325 | if ($trace['function']) { |
||
326 | $content .= sprintf('from <span class="trace-class">%s</span><span class="trace-type">%s</span><span class="trace-method">%s</span>(<span class="trace-arguments">%s</span>)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); |
||
327 | } |
||
328 | |||
329 | if (isset($trace['file']) && isset($trace['line'])) { |
||
330 | $content .= $this->formatPath($trace['file'], $trace['line']); |
||
331 | } |
||
332 | |||
333 | $content .= "</td></tr>\n"; |
||
334 | } |
||
335 | |||
336 | $content .= "</table>\n</div>\n"; |
||
337 | } |
||
338 | } catch (Exception $e) { |
||
339 | if ($this->debug) { |
||
340 | $e = FlattenException::make($e); |
||
341 | $title = sprintf('Exception thrown when handling an exception: (%s: %s)', |
||
342 | $e->getClass(), |
||
343 | $this->escapeHtml($e->getMessage()) |
||
344 | ); |
||
345 | } else { |
||
346 | $title = 'Whoops, looks like something went wrong'; |
||
347 | } |
||
348 | } |
||
349 | |||
350 | return <<<EOF |
||
351 | <div class="exception"> |
||
352 | <div class="container"> |
||
353 | <div class="exception-wrapper"> |
||
354 | <h1 class="break-long-words exception-message">$title</h1> |
||
355 | </div> |
||
356 | </div> |
||
357 | </div> |
||
358 | |||
359 | <div class="container"> |
||
360 | $content |
||
361 | </div> |
||
362 | EOF; |
||
363 | } |
||
364 | |||
365 | public function getStylesheet() |
||
366 | { |
||
367 | if ( ! $this->debug) { |
||
368 | return <<<'EOF' |
||
369 | body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; overflow: hidden; } |
||
370 | .container { display: flex; align-items: center; justify-content: center; height: 100vh; width: 100vw; } |
||
371 | h1 { color: #586d7e; font-size: 2em; text-shadow: none; word-break: break-all; word-break: break-word; } |
||
372 | EOF; |
||
373 | } |
||
374 | |||
375 | return <<<'EOF' |
||
376 | body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } |
||
377 | |||
378 | a { cursor: pointer; text-decoration: none; } |
||
379 | a:hover { text-decoration: underline; } |
||
380 | abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } |
||
381 | |||
382 | code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } |
||
383 | |||
384 | table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } |
||
385 | table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } |
||
386 | table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } |
||
387 | table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } |
||
388 | |||
389 | .hidden-xs-down { display: none; } |
||
390 | .block { display: block; } |
||
391 | .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } |
||
392 | .text-muted { color: #999; } |
||
393 | |||
394 | .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } |
||
395 | .container::after { content: ""; display: table; clear: both; } |
||
396 | |||
397 | .exception { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } |
||
398 | |||
399 | .exception-wrapper { display: flex; align-items: center; min-height: 70px; } |
||
400 | .exception-message { flex-grow: 1; padding: 30px 0; text-shadow: none; } |
||
401 | .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } |
||
402 | .exception-message.long { font-size: 18px; } |
||
403 | .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } |
||
404 | .exception-message a:hover { border-bottom-color: #ffffff; } |
||
405 | |||
406 | .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } |
||
407 | |||
408 | .trace + .trace { margin-top: 30px; } |
||
409 | .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } |
||
410 | |||
411 | .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } |
||
412 | |||
413 | .trace-file, .trace-file a { color: #222; margin-top: 3px; font-size: 13px; } |
||
414 | .trace-class { color: #B0413E; } |
||
415 | .trace-type { padding: 0 2px; } |
||
416 | .trace-method { color: #B0413E; font-weight: bold; } |
||
417 | .trace-args { color: #777; font-weight: normal; padding-left: 2px; } |
||
418 | |||
419 | @media (min-width: 575px) { |
||
420 | .hidden-xs-down { display: initial; } |
||
421 | } |
||
422 | EOF; |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Gets the format class where the exception. |
||
427 | * |
||
428 | * @param string $class |
||
429 | * |
||
430 | * @return string |
||
431 | */ |
||
432 | private function formatClass($class) |
||
433 | { |
||
434 | $parts = explode('\\', $class); |
||
435 | |||
436 | return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts)); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Gets the path file with you line code. |
||
441 | * |
||
442 | * @param string $path |
||
443 | * @param int $line |
||
444 | * |
||
445 | * @return string |
||
446 | */ |
||
447 | private function formatPath($path, $line) |
||
448 | { |
||
449 | $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); |
||
450 | $frmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); |
||
451 | |||
452 | if ( ! $frmt) { |
||
453 | return sprintf('<span class="block trace-file">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', |
||
454 | $this->escapeHtml($path), |
||
455 | $file, |
||
456 | 0 < $line ? ' line '.$line : '' |
||
457 | ); |
||
458 | } |
||
459 | |||
460 | if (is_string($frmt)) { |
||
461 | $index = strpos($f = $frmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l')) ?: strlen($f)); |
||
462 | $frmt = [substr($f, 0, $index)] + preg_split('/&([^>]++)>/', substr($f, $index), -1, PREG_SPLIT_DELIM_CAPTURE); |
||
463 | |||
464 | for ($index = 1; isset($frmt[$index]); ++$index) { |
||
465 | if (strpos($path, $k = $frmt[$index++])) { |
||
466 | $path = substr_replace($path, $frmt[$index], 0, strlen($k)); |
||
467 | break; |
||
468 | } |
||
469 | } |
||
470 | |||
471 | $data = strstr($frmt[0], ['%f' => $file, '%l' => $line]); |
||
472 | } else { |
||
473 | try { |
||
474 | $data = $frmt->format($file, $line); |
||
475 | } catch (Exception $e) { |
||
476 | return sprintf('<span class="block trace-file-path">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', |
||
477 | $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); |
||
478 | } |
||
479 | } |
||
480 | |||
481 | return sprintf('<span class="block trace-file">in <a href="%s" title="Go to source"><b>%s</b>%s</a></span>', |
||
482 | $this->escapeHtml($data), $file, $line > 0 ? ' line '.$line : ''); |
||
483 | } |
||
484 | |||
485 | /** |
||
486 | * Formats an array as a string. |
||
487 | * |
||
488 | * @param array $args |
||
489 | * |
||
490 | * @return string |
||
491 | */ |
||
492 | private function formatArgs(array $args) |
||
515 | } |
||
516 | |||
517 | /** |
||
518 | * Gets HTML-encode as a string. |
||
519 | * |
||
520 | * @param string $string |
||
521 | * |
||
522 | * @return string |
||
523 | */ |
||
524 | private function escapeHtml($string) |
||
527 | } |
||
528 | } |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths