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: