1: <?php
2: 3: 4: 5: 6: 7:
8:
9: namespace Wei;
10:
11: 12: 13: 14: 15: 16:
17: class Error extends Base
18: {
19: 20: 21: 22: 23:
24: protected $message = 'Error';
25:
26: 27: 28: 29: 30:
31: protected $detail = 'Unfortunately, an error occurred. Please try again later.';
32:
33: 34: 35: 36: 37:
38: protected $notFoundDetail = 'Sorry, the page you requested was not found. Please check the URL and try again.';
39:
40: 41: 42: 43: 44: 45:
46: protected $ignorePrevHandler = false;
47:
48: 49: 50: 51: 52:
53: protected $prevExceptionHandler;
54:
55: 56: 57: 58: 59:
60: protected $handlers = array(
61: 'error' => array(),
62: 'fatal' => array(),
63: 'notFound' => array()
64: );
65:
66: 67: 68: 69: 70:
71: public function __construct($options = array())
72: {
73: parent::__construct($options);
74:
75: $this->registerErrorHandler();
76: $this->registerExceptionHandler();
77: $this->registerFatalHandler();
78: }
79:
80: 81: 82: 83: 84: 85:
86: public function __invoke($fn)
87: {
88: $this->handlers['error'][] = $fn;
89: return $this;
90: }
91:
92: 93: 94: 95: 96: 97:
98: public function notFound($fn)
99: {
100: $this->handlers['notFound'][] = $fn;
101: return $this;
102: }
103:
104: 105: 106: 107: 108: 109:
110: public function fatal($fn)
111: {
112: $this->handlers['fatal'][] = $fn;
113: return $this;
114: }
115:
116: 117: 118:
119: protected function registerExceptionHandler()
120: {
121: $this->prevExceptionHandler = set_exception_handler(array($this, 'handleException'));
122: }
123:
124: 125: 126:
127: protected function registerErrorHandler()
128: {
129: set_error_handler(array($this, 'handleError'));
130: }
131:
132: 133: 134:
135: protected function registerFatalHandler()
136: {
137: $error = $this;
138:
139:
140:
141: $cwd = getcwd();
142:
143: register_shutdown_function(function() use($error, $cwd) {
144: $e = error_get_last();
145: if (!$e || !in_array($e['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
146:
147: return;
148: }
149:
150: ob_get_length() && ob_end_clean();
151:
152:
153: chdir($cwd);
154:
155: $exception = new \ErrorException($e['message'], $e['type'], 0, $e['file'], $e['line']);
156:
157: if ($error->triggerHandler('fatal', $exception)) {
158:
159: return;
160: }
161:
162:
163: if ($error->triggerHandler('error', $exception)) {
164:
165: return;
166: }
167:
168:
169: $error->internalHandleException($exception);
170: });
171: }
172:
173: 174: 175: 176: 177: 178: 179:
180: public function triggerHandler($type, \Exception $exception)
181: {
182: foreach ($this->handlers[$type] as $handler) {
183: $result = call_user_func_array($handler, array($exception, $this->wei));
184: if (true === $result) {
185: return true;
186: }
187: }
188: return false;
189: }
190:
191: 192: 193: 194: 195:
196: public function handleException(\Exception $exception)
197: {
198: if (!$this->ignorePrevHandler && $this->prevExceptionHandler) {
199: call_user_func($this->prevExceptionHandler, $exception);
200: }
201:
202: if (404 == $exception->getCode()) {
203: if ($this->triggerHandler('notFound', $exception)) {
204: return;
205: }
206: }
207:
208: if (!$this->triggerHandler('error', $exception)) {
209: $this->internalHandleException($exception);
210: }
211:
212: restore_exception_handler();
213: }
214:
215: public function internalHandleException(\Exception $exception)
216: {
217: $debug = $this->wei->isDebug();
218: $code = $exception->getCode();
219:
220:
221: if ($code < 100 || $code > 600) {
222: $code = 500;
223: }
224:
225:
226: if ($code >= 500) {
227: $level = 'critical';
228: } else {
229: $level = 'info';
230: }
231:
232: try {
233:
234: $this->response->setStatusCode($code)->send();
235: $this->logger->log($level, $exception);
236:
237: $this->renderException($exception, $debug);
238: } catch (\Exception $e) {
239: $this->renderException($e, $debug);
240: }
241: }
242:
243: 244: 245: 246: 247: 248:
249: public function renderException(\Exception $e, $debug)
250: {
251: $code = $e->getCode();
252: $file = $e->getFile();
253: $line = $e->getLine();
254:
255:
256: if ($debug) {
257: $message = $e->getMessage();
258: $detail = sprintf('Threw by %s in %s on line %s', get_class($e), $file, $line);
259: } else {
260: $message = $this->message;
261: $detail = $this->detail;
262: }
263:
264: if ($code == 404) {
265: $message = $e->getMessage();
266: if (!$debug) {
267: $detail = $this->notFoundDetail;
268: }
269: }
270: $title = htmlspecialchars($message, ENT_QUOTES);
271: $message = nl2br($title);
272:
273:
274: if ($debug) {
275: $fileInfo = $this->getFileCode($file, $line);
276: $trace = htmlspecialchars($e->getTraceAsString(), ENT_QUOTES);
277:
278: $detail = "<h2>File</h2>"
279: . "<p class=\"error-text\">$file</p>"
280: . "<p><pre>$fileInfo</pre></p>"
281: . "<h2>Trace</h2>"
282: . "<p class=\"error-text\">$detail</p>"
283: . "<p><pre>$trace</pre></p>";
284: } else {
285: $detail = "<p>$detail</p>";
286: }
287:
288: $html = '<!DOCTYPE html>'
289: . '<html>'
290: . '<head>'
291: . '<meta name="viewport" content="width=device-width">'
292: . '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
293: . "<title>$title</title>"
294: . '<style type="text/css">'
295: . 'body { font-size: 12px; color: #333; padding: 15px 20px 20px 20px; }'
296: . 'h1, h2, p, pre { margin: 0; padding: 0; }'
297: . 'body, pre { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif, "\5fae\8f6f\96c5\9ed1", "\5b8b\4f53"; }'
298: . 'h1 { font-size: 36px; }'
299: . 'h2 { font-size: 20px; margin: 20px 0 0; }'
300: . 'pre { line-height: 18px; }'
301: . 'strong, .error-text { color: #FF3000; }'
302: . '</style>'
303: . '</head>'
304: . '<body>'
305: . "<h1>$message</h1>"
306: . $detail
307: . '</body>'
308: . '</html>';
309:
310: echo $html;
311: }
312:
313: 314: 315: 316: 317: 318: 319: 320: 321: 322:
323: public function handleError($code, $message, $file, $line)
324: {
325: if (!(error_reporting() & $code)) {
326:
327: return;
328: }
329: restore_error_handler();
330: throw new \ErrorException($message, $code, 500, $file, $line);
331: }
332:
333: 334: 335: 336: 337: 338: 339: 340:
341: public function getFileCode($file, $line, $range = 20)
342: {
343: $code = file($file);
344: $half = (int) ($range / 2);
345:
346: $start = $line - $half;
347: 0 > $start && $start = 0;
348:
349: $total = count($code);
350: $end = $line + $half;
351: $total < $end && $end = $total;
352:
353: $len = strlen($end);
354:
355: array_unshift($code, null);
356: $content = '';
357: for ($i = $start; $i < $end; $i++) {
358: $temp = str_pad($i, $len, 0, STR_PAD_LEFT) . ': ' . $code[$i];
359: if ($line != $i) {
360: $content .= htmlspecialchars($temp, ENT_QUOTES);
361: } else {
362: $content .= '<strong>' . htmlspecialchars($temp, ENT_QUOTES) . '</strong>';
363: }
364: }
365:
366: return $content;
367: }
368: }
369: