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 |