1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Error;
16:
17: use Cake\Core\Configure;
18: use Cake\Log\Log;
19: use Cake\Routing\Router;
20: use Error;
21: use Exception;
22:
23: 24: 25: 26: 27: 28: 29:
30: abstract class BaseErrorHandler
31: {
32:
33: 34: 35: 36: 37:
38: protected $_options = [];
39:
40: 41: 42:
43: protected $_handled = false;
44:
45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
55: abstract protected function _displayError($error, $debug);
56:
57: 58: 59: 60: 61: 62: 63: 64: 65:
66: abstract protected function _displayException($exception);
67:
68: 69: 70: 71: 72:
73: public function register()
74: {
75: $level = -1;
76: if (isset($this->_options['errorLevel'])) {
77: $level = $this->_options['errorLevel'];
78: }
79: error_reporting($level);
80: set_error_handler([$this, 'handleError'], $level);
81: set_exception_handler([$this, 'wrapAndHandleException']);
82: register_shutdown_function(function () {
83: if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && $this->_handled) {
84: return;
85: }
86: $megabytes = Configure::read('Error.extraFatalErrorMemory');
87: if ($megabytes === null) {
88: $megabytes = 4;
89: }
90: if ($megabytes > 0) {
91: $this->increaseMemoryLimit($megabytes * 1024);
92: }
93: $error = error_get_last();
94: if (!is_array($error)) {
95: return;
96: }
97: $fatals = [
98: E_USER_ERROR,
99: E_ERROR,
100: E_PARSE,
101: ];
102: if (!in_array($error['type'], $fatals, true)) {
103: return;
104: }
105: $this->handleFatalError(
106: $error['type'],
107: $error['message'],
108: $error['file'],
109: $error['line']
110: );
111: });
112: }
113:
114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130:
131: public function handleError($code, $description, $file = null, $line = null, $context = null)
132: {
133: if (error_reporting() === 0) {
134: return false;
135: }
136: $this->_handled = true;
137: list($error, $log) = static::mapErrorCode($code);
138: if ($log === LOG_ERR) {
139: return $this->handleFatalError($code, $description, $file, $line);
140: }
141: $data = [
142: 'level' => $log,
143: 'code' => $code,
144: 'error' => $error,
145: 'description' => $description,
146: 'file' => $file,
147: 'line' => $line,
148: ];
149:
150: $debug = Configure::read('debug');
151: if ($debug) {
152: $data += [
153: 'context' => $context,
154: 'start' => 3,
155: 'path' => Debugger::trimPath($file)
156: ];
157: }
158: $this->_displayError($data, $debug);
159: $this->_logError($log, $data);
160:
161: return true;
162: }
163:
164: 165: 166: 167: 168: 169: 170: 171:
172: public function wrapAndHandleException($exception)
173: {
174: if ($exception instanceof Error) {
175: $exception = new PHP7ErrorException($exception);
176: }
177: $this->handleException($exception);
178: }
179:
180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190:
191: public function handleException(Exception $exception)
192: {
193: $this->_displayException($exception);
194: $this->_logException($exception);
195: $this->_stop($exception->getCode() ?: 1);
196: }
197:
198: 199: 200: 201: 202: 203: 204: 205:
206: protected function _stop($code)
207: {
208:
209: }
210:
211: 212: 213: 214: 215: 216: 217: 218: 219:
220: public function handleFatalError($code, $description, $file, $line)
221: {
222: $data = [
223: 'code' => $code,
224: 'description' => $description,
225: 'file' => $file,
226: 'line' => $line,
227: 'error' => 'Fatal Error',
228: ];
229: $this->_logError(LOG_ERR, $data);
230:
231: $this->handleException(new FatalErrorException($description, 500, $file, $line));
232:
233: return true;
234: }
235:
236: 237: 238: 239: 240: 241: 242:
243: public function increaseMemoryLimit($additionalKb)
244: {
245: $limit = ini_get('memory_limit');
246: if (!strlen($limit) || $limit === '-1') {
247: return;
248: }
249: $limit = trim($limit);
250: $units = strtoupper(substr($limit, -1));
251: $current = (int)substr($limit, 0, strlen($limit) - 1);
252: if ($units === 'M') {
253: $current *= 1024;
254: $units = 'K';
255: }
256: if ($units === 'G') {
257: $current = $current * 1024 * 1024;
258: $units = 'K';
259: }
260:
261: if ($units === 'K') {
262: ini_set('memory_limit', ceil($current + $additionalKb) . 'K');
263: }
264: }
265:
266: 267: 268: 269: 270: 271: 272:
273: protected function _logError($level, $data)
274: {
275: $message = sprintf(
276: '%s (%s): %s in [%s, line %s]',
277: $data['error'],
278: $data['code'],
279: $data['description'],
280: $data['file'],
281: $data['line']
282: );
283: if (!empty($this->_options['trace'])) {
284: $trace = Debugger::trace([
285: 'start' => 1,
286: 'format' => 'log'
287: ]);
288:
289: $request = Router::getRequest();
290: if ($request) {
291: $message .= $this->_requestContext($request);
292: }
293: $message .= "\nTrace:\n" . $trace . "\n";
294: }
295: $message .= "\n\n";
296:
297: return Log::write($level, $message);
298: }
299:
300: 301: 302: 303: 304: 305:
306: protected function _logException(Exception $exception)
307: {
308: $config = $this->_options;
309: $unwrapped = $exception instanceof PHP7ErrorException ?
310: $exception->getError() :
311: $exception;
312:
313: if (empty($config['log'])) {
314: return false;
315: }
316:
317: if (!empty($config['skipLog'])) {
318: foreach ((array)$config['skipLog'] as $class) {
319: if ($unwrapped instanceof $class) {
320: return false;
321: }
322: }
323: }
324:
325: return Log::error($this->_getMessage($exception));
326: }
327:
328: 329: 330: 331: 332: 333:
334: protected function _requestContext($request)
335: {
336: $message = "\nRequest URL: " . $request->getRequestTarget();
337:
338: $referer = $request->getEnv('HTTP_REFERER');
339: if ($referer) {
340: $message .= "\nReferer URL: " . $referer;
341: }
342: $clientIp = $request->clientIp();
343: if ($clientIp && $clientIp !== '::1') {
344: $message .= "\nClient IP: " . $clientIp;
345: }
346:
347: return $message;
348: }
349:
350: 351: 352: 353: 354: 355:
356: protected function _getMessage(Exception $exception)
357: {
358: $message = $this->getMessageForException($exception);
359:
360: $request = Router::getRequest();
361: if ($request) {
362: $message .= $this->_requestContext($request);
363: }
364:
365: return $message;
366: }
367:
368: 369: 370: 371: 372: 373: 374:
375: protected function getMessageForException($exception, $isPrevious = false)
376: {
377: $exception = $exception instanceof PHP7ErrorException ?
378: $exception->getError() :
379: $exception;
380: $config = $this->_options;
381:
382: $message = sprintf(
383: '%s[%s] %s in %s on line %s',
384: $isPrevious ? "\nCaused by: " : '',
385: get_class($exception),
386: $exception->getMessage(),
387: $exception->getFile(),
388: $exception->getLine()
389: );
390: $debug = Configure::read('debug');
391:
392: if ($debug && method_exists($exception, 'getAttributes')) {
393: $attributes = $exception->getAttributes();
394: if ($attributes) {
395: $message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
396: }
397: }
398:
399: if (!empty($config['trace'])) {
400: $message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n\n";
401: }
402:
403: $previous = $exception->getPrevious();
404: if ($previous) {
405: $message .= $this->getMessageForException($previous, true);
406: }
407:
408: return $message;
409: }
410:
411: 412: 413: 414: 415: 416:
417: public static function mapErrorCode($code)
418: {
419: $levelMap = [
420: E_PARSE => 'error',
421: E_ERROR => 'error',
422: E_CORE_ERROR => 'error',
423: E_COMPILE_ERROR => 'error',
424: E_USER_ERROR => 'error',
425: E_WARNING => 'warning',
426: E_USER_WARNING => 'warning',
427: E_COMPILE_WARNING => 'warning',
428: E_RECOVERABLE_ERROR => 'warning',
429: E_NOTICE => 'notice',
430: E_USER_NOTICE => 'notice',
431: E_STRICT => 'strict',
432: E_DEPRECATED => 'deprecated',
433: E_USER_DEPRECATED => 'deprecated',
434: ];
435: $logMap = [
436: 'error' => LOG_ERR,
437: 'warning' => LOG_WARNING,
438: 'notice' => LOG_NOTICE,
439: 'strict' => LOG_NOTICE,
440: 'deprecated' => LOG_NOTICE,
441: ];
442:
443: $error = $levelMap[$code];
444: $log = $logMap[$error];
445:
446: return [ucfirst($error), $log];
447: }
448: }
449: