| Total Complexity | 43 |
| Total Lines | 329 |
| Duplicated Lines | 0 % |
| Changes | 2 | ||
| Bugs | 0 | Features | 0 |
Complex classes like GenericExceptionHandler 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 GenericExceptionHandler, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 13 | class GenericExceptionHandler |
||
| 14 | { |
||
| 15 | /** |
||
| 16 | * Whether the `GenericExceptionHandler` should handle exceptions. Defaults to true |
||
| 17 | * @var boolean |
||
| 18 | */ |
||
| 19 | public static $enabled = true; |
||
| 20 | |||
| 21 | /** |
||
| 22 | * An instance of the Symphony Log class, used to write errors to the log |
||
| 23 | * @var Log |
||
| 24 | */ |
||
| 25 | private static $_Log = null; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * Initialise will set the error handler to be the `__CLASS__::handler` function. |
||
| 29 | * |
||
| 30 | * @param Log $Log |
||
| 31 | * An instance of a Symphony Log object to write errors to |
||
| 32 | */ |
||
| 33 | public static function initialise(Log $Log = null) |
||
| 34 | { |
||
| 35 | if (!is_null($Log)) { |
||
| 36 | self::$_Log = $Log; |
||
| 37 | } |
||
| 38 | |||
| 39 | set_exception_handler(array(__CLASS__, 'handler')); |
||
| 40 | register_shutdown_function(array(__CLASS__, 'shutdown')); |
||
| 41 | } |
||
| 42 | |||
| 43 | /** |
||
| 44 | * Retrieves a window of lines before and after the line where the error |
||
| 45 | * occurred so that a developer can help debug the exception |
||
| 46 | * |
||
| 47 | * @param integer $line |
||
| 48 | * The line where the error occurred. |
||
| 49 | * @param string $file |
||
| 50 | * The file that holds the logic that caused the error. |
||
| 51 | * @param integer $window |
||
| 52 | * The number of lines either side of the line where the error occurred |
||
| 53 | * to show |
||
| 54 | * @return array |
||
| 55 | */ |
||
| 56 | protected static function __nearbyLines($line, $file, $window = 5) |
||
| 57 | { |
||
| 58 | if (!file_exists($file)) { |
||
| 59 | return array(); |
||
| 60 | } |
||
| 61 | |||
| 62 | return array_slice(file($file), ($line - 1) - $window, $window * 2, true); |
||
| 63 | } |
||
| 64 | |||
| 65 | /** |
||
| 66 | * This function's goal is to validate the `$e` parameter in order to ensure |
||
| 67 | * that the object is an `Exception` or a `Throwable` instance. |
||
| 68 | * @since Symphony 2.7.0 |
||
| 69 | * |
||
| 70 | * @param Throwable $e |
||
| 71 | * The Throwable object that will be validated |
||
| 72 | * @return boolean |
||
| 73 | * true when valid, false otherwise |
||
| 74 | */ |
||
| 75 | public static function isValidThrowable($e) |
||
| 78 | } |
||
| 79 | |||
| 80 | /** |
||
| 81 | * The handler function is given an Throwable and will call it's render |
||
| 82 | * function to display the Throwable to a user. After calling the render |
||
| 83 | * function, the output is displayed and then exited to prevent any further |
||
| 84 | * logic from occurring. |
||
| 85 | * |
||
| 86 | * @since Symphony 2.7.0 |
||
| 87 | * This function works with both Exception and Throwable |
||
| 88 | * Supporting both PHP 5.6 and 7 forces use to not qualify the $e parameter |
||
| 89 | * |
||
| 90 | * @param Throwable $e |
||
| 91 | * The Throwable object |
||
| 92 | * @return string |
||
| 93 | * The result of the Throwable's render function |
||
| 94 | */ |
||
| 95 | public static function handler($e) |
||
| 96 | { |
||
| 97 | $output = ''; |
||
| 98 | |||
| 99 | try { |
||
| 100 | // Instead of just throwing an empty page, return a 404 page. |
||
| 101 | if (self::$enabled !== true) { |
||
| 102 | $e = new FrontendPageNotFoundException(); |
||
| 103 | } |
||
| 104 | |||
| 105 | // Validate the type, resolve to a 404 if not valid |
||
| 106 | if (!static::isValidThrowable($e)) { |
||
| 107 | $e = new FrontendPageNotFoundException(); |
||
| 108 | } |
||
| 109 | |||
| 110 | $exception_type = get_class($e); |
||
| 111 | |||
| 112 | if (class_exists("{$exception_type}Handler") && method_exists("{$exception_type}Handler", 'render')) { |
||
| 113 | $class = "{$exception_type}Handler"; |
||
| 114 | } else { |
||
| 115 | $class = __CLASS__; |
||
| 116 | } |
||
| 117 | |||
| 118 | // Exceptions should be logged if they are not caught. |
||
| 119 | if (self::$_Log instanceof Log) { |
||
| 120 | self::$_Log->pushExceptionToLog($e, true); |
||
| 121 | } |
||
| 122 | |||
| 123 | $output = call_user_func(array($class, 'render'), $e); |
||
| 124 | |||
| 125 | // If an exception was raised trying to render the exception, fall back |
||
| 126 | // to the generic exception handler |
||
| 127 | } catch (Exception $e) { |
||
| 128 | try { |
||
| 129 | $output = call_user_func(array('GenericExceptionHandler', 'render'), $e); |
||
| 130 | |||
| 131 | // If the generic exception handler couldn't do it, well we're in bad |
||
| 132 | // shape, just output a plaintext response! |
||
| 133 | } catch (Exception $e) { |
||
| 134 | echo "<pre>"; |
||
| 135 | echo 'A severe error occurred whilst trying to handle an exception, check the Symphony log for more details' . PHP_EOL; |
||
| 136 | echo $e->getMessage() . ' on ' . $e->getLine() . ' of file ' . $e->getFile() . PHP_EOL; |
||
| 137 | exit; |
||
| 138 | } |
||
| 139 | } |
||
| 140 | |||
| 141 | // Pending nothing disasterous, we should have `$e` and `$output` values here. |
||
| 142 | if (!headers_sent()) { |
||
| 143 | cleanup_session_cookies(); |
||
| 144 | |||
| 145 | // Inspect the exception to determine the best status code |
||
| 146 | $httpStatus = null; |
||
| 147 | if ($e instanceof SymphonyErrorPage) { |
||
| 148 | $httpStatus = $e->getHttpStatusCode(); |
||
| 149 | } elseif ($e instanceof FrontendPageNotFoundException) { |
||
| 150 | $httpStatus = Page::HTTP_STATUS_NOT_FOUND; |
||
| 151 | } |
||
| 152 | |||
| 153 | if (!$httpStatus || $httpStatus == Page::HTTP_STATUS_OK) { |
||
| 154 | $httpStatus = Page::HTTP_STATUS_ERROR; |
||
| 155 | } |
||
| 156 | |||
| 157 | Page::renderStatusCode($httpStatus); |
||
| 158 | header('Content-Type: text/html; charset=utf-8'); |
||
| 159 | } |
||
| 160 | |||
| 161 | echo $output; |
||
| 162 | exit; |
||
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * Returns the path to the error-template by looking at the `WORKSPACE/template/` |
||
| 167 | * directory, then at the `TEMPLATES` directory for the convention `*.tpl`. If |
||
| 168 | * the template is not found, `false` is returned |
||
| 169 | * |
||
| 170 | * @since Symphony 2.3 |
||
| 171 | * @param string $name |
||
| 172 | * Name of the template |
||
| 173 | * @return string|false |
||
| 174 | * String, which is the path to the template if the template is found, |
||
| 175 | * false otherwise |
||
| 176 | */ |
||
| 177 | public static function getTemplate($name) |
||
| 178 | { |
||
| 179 | $format = '%s/%s.tpl'; |
||
| 180 | |||
| 181 | if (file_exists($template = sprintf($format, WORKSPACE . '/template', $name))) { |
||
| 182 | return $template; |
||
| 183 | } elseif (file_exists($template = sprintf($format, TEMPLATE, $name))) { |
||
| 184 | return $template; |
||
| 185 | } else { |
||
| 186 | return false; |
||
| 187 | } |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * The render function will take an Throwable and output a HTML page |
||
| 192 | * |
||
| 193 | * @since Symphony 2.7.0 |
||
| 194 | * This function works with both Exception and Throwable |
||
| 195 | * |
||
| 196 | * @param Throwable $e |
||
| 197 | * The Throwable object |
||
| 198 | * @return string |
||
| 199 | * An HTML string |
||
| 200 | */ |
||
| 201 | public static function render($e) |
||
| 202 | { |
||
| 203 | $lines = null; |
||
| 204 | |||
| 205 | foreach (self::__nearByLines($e->getLine(), $e->getFile()) as $line => $string) { |
||
| 206 | $lines .= sprintf( |
||
| 207 | '<li%s><strong>%d</strong> <code>%s</code></li>', |
||
| 208 | (($line+1) == $e->getLine() ? ' class="error"' : null), |
||
| 209 | ++$line, |
||
| 210 | str_replace("\t", ' ', htmlspecialchars($string)) |
||
| 211 | ); |
||
| 212 | } |
||
| 213 | |||
| 214 | $trace = null; |
||
| 215 | |||
| 216 | foreach ($e->getTrace() as $t) { |
||
| 217 | $trace .= sprintf( |
||
| 218 | '<li><code><em>[%s:%d]</em></code></li><li><code>    %s%s%s();</code></li>', |
||
| 219 | (isset($t['file']) ? $t['file'] : null), |
||
| 220 | (isset($t['line']) ? $t['line'] : null), |
||
| 221 | (isset($t['class']) ? $t['class'] : null), |
||
| 222 | (isset($t['type']) ? $t['type'] : null), |
||
| 223 | $t['function'] |
||
| 224 | ); |
||
| 225 | } |
||
| 226 | |||
| 227 | $queries = null; |
||
| 228 | |||
| 229 | if (is_object(Symphony::Database())) { |
||
| 230 | $debug = Symphony::Database()->debug(); |
||
| 231 | |||
| 232 | if (!empty($debug)) { |
||
| 233 | foreach ($debug as $query) { |
||
| 234 | $queries .= sprintf( |
||
| 235 | '<li><em>[%01.4f]</em><code> %s;</code> </li>', |
||
| 236 | (isset($query['execution_time']) ? $query['execution_time'] : null), |
||
| 237 | htmlspecialchars($query['query']) |
||
| 238 | ); |
||
| 239 | } |
||
| 240 | } |
||
| 241 | } |
||
| 242 | |||
| 243 | return self::renderHtml( |
||
| 244 | 'fatalerror.generic', |
||
| 245 | ($e instanceof ErrorException ? GenericErrorHandler::$errorTypeStrings[$e->getSeverity()] : 'Fatal Error'), |
||
| 246 | $e->getMessage(), |
||
| 247 | $e->getFile(), |
||
| 248 | $e->getLine(), |
||
| 249 | $lines, |
||
| 250 | $trace, |
||
| 251 | $queries |
||
| 252 | ); |
||
| 253 | } |
||
| 254 | |||
| 255 | /** |
||
| 256 | * The shutdown function will capture any fatal errors and format them as a |
||
| 257 | * usual Symphony page. |
||
| 258 | * |
||
| 259 | * @since Symphony 2.4 |
||
| 260 | */ |
||
| 261 | public static function shutdown() |
||
| 262 | { |
||
| 263 | $last_error = error_get_last(); |
||
| 264 | |||
| 265 | if (!is_null($last_error) && $last_error['type'] === E_ERROR) { |
||
| 266 | $code = $last_error['type']; |
||
| 267 | $message = $last_error['message']; |
||
| 268 | $file = $last_error['file']; |
||
| 269 | $line = $last_error['line']; |
||
| 270 | |||
| 271 | try { |
||
| 272 | // Log the error message |
||
| 273 | if (self::$_Log instanceof Log) { |
||
| 274 | self::$_Log->pushToLog(sprintf( |
||
| 275 | '%s %s: %s%s%s', |
||
| 276 | __CLASS__, |
||
| 277 | $code, |
||
| 278 | $message, |
||
| 279 | ($line ? " on line $line" : null), |
||
| 280 | ($file ? " of file $file" : null) |
||
| 281 | ), $code, true); |
||
| 282 | } |
||
| 283 | |||
| 284 | ob_clean(); |
||
| 285 | |||
| 286 | // Display the error message |
||
| 287 | echo self::renderHtml( |
||
| 288 | 'fatalerror.fatal', |
||
| 289 | 'Fatal Error', |
||
| 290 | $message, |
||
| 291 | $file, |
||
| 292 | $line |
||
| 293 | ); |
||
| 294 | } catch (Exception $e) { |
||
| 295 | echo "<pre>"; |
||
| 296 | echo 'A severe error occurred whilst trying to handle an exception, check the Symphony log for more details' . PHP_EOL; |
||
| 297 | echo $e->getMessage() . ' on ' . $e->getLine() . ' of file ' . $e->getFile() . PHP_EOL; |
||
| 298 | } |
||
| 299 | } |
||
| 300 | } |
||
| 301 | |||
| 302 | /** |
||
| 303 | * This function will fetch the desired `$template`, and output the |
||
| 304 | * Throwable in a user friendly way. |
||
| 305 | * |
||
| 306 | * @since Symphony 2.4 |
||
| 307 | * @param string $template |
||
| 308 | * The template name, which should correspond to something in the TEMPLATE |
||
| 309 | * directory, eg `fatalerror.fatal`. |
||
| 310 | * |
||
| 311 | * @since Symphony 2.7.0 |
||
| 312 | * This function works with both Exception and Throwable |
||
| 313 | * |
||
| 314 | * @param string $heading |
||
| 315 | * @param string $message |
||
| 316 | * @param string $file |
||
| 317 | * @param string $line |
||
| 318 | * @param string $lines |
||
| 319 | * @param string $trace |
||
| 320 | * @param string $queries |
||
| 321 | * @return string |
||
| 322 | * The HTML of the formatted error message. |
||
| 323 | */ |
||
| 324 | public static function renderHtml($template, $heading, $message, $file = null, $line = null, $lines = null, $trace = null, $queries = null) |
||
| 342 | } |
||
| 343 | } |
||
| 344 | |||
| 345 | /** |
||
| 346 | * `GenericErrorHandler` will catch any warnings or notices thrown by PHP and |
||
| 347 | * raise the errors to Exceptions so they can be dealt with by the |
||
| 348 | * `GenericExceptionHandler`. The type of errors that are raised to Exceptions |
||
| 474 |