From 048410164d2a17bbb588e43351e44eeb81610960 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Thu, 4 Jun 2015 22:09:05 +0200 Subject: Refactor exception handling into dedicated class Signed-off-by: Florian Pritz --- application/controllers/api.php | 2 +- application/controllers/tools.php | 2 +- application/libraries/ExceptionHandler.php | 154 +++++++++++++++++++++++++++++ index.php | 140 +------------------------- 4 files changed, 158 insertions(+), 140 deletions(-) create mode 100644 application/libraries/ExceptionHandler.php diff --git a/application/controllers/api.php b/application/controllers/api.php index 644a726e7..9540f1ff7 100644 --- a/application/controllers/api.php +++ b/application/controllers/api.php @@ -59,7 +59,7 @@ class Api extends MY_Controller { } catch (\exceptions\PublicApiException $e) { return send_json_error_reply($e->get_error_id(), $e->getMessage(), $e->get_data()); } catch (\Exception $e) { - _log_exception($e); + \libraries\ExceptionHandler::log_exception($e); return send_json_error_reply("internal-error", "An unhandled internal server error occured"); } } diff --git a/application/controllers/tools.php b/application/controllers/tools.php index f2fa1f97c..f0d8ce6f9 100644 --- a/application/controllers/tools.php +++ b/application/controllers/tools.php @@ -89,7 +89,7 @@ class Tools extends MY_Controller { $test->cleanup(); } catch (\Exception $e) { echo "not ok - uncaught exception in {$testcase}->{$method->name}\n"; - _actual_exception_handler($e); + \libraries\ExceptionHandler::exception_handler($e); $exitcode = 255; } } diff --git a/application/libraries/ExceptionHandler.php b/application/libraries/ExceptionHandler.php new file mode 100644 index 000000000..7dc954f2b --- /dev/null +++ b/application/libraries/ExceptionHandler.php @@ -0,0 +1,154 @@ + + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace libraries; + +class ExceptionHandler { + static function setup() + { + set_error_handler(array("\libraries\ExceptionHandler", "error_handler")); + set_exception_handler(array("\libraries\ExceptionHandler", 'exception_handler')); + register_shutdown_function(array("\libraries\ExceptionHandler", "check_for_fatal")); + assert_options(ASSERT_ACTIVE, true); + assert_options(ASSERT_CALLBACK, array("\libraries\ExceptionHandler", '_assert_failure')); + } + + static function error_handler($errno, $errstr, $errfile, $errline) + { + if (!(error_reporting() & $errno)) { + // This error code is not included in error_reporting + return; + } + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + } + + // Source: https://gist.github.com/abtris/1437966 + static private function getExceptionTraceAsString($exception) { + $rtn = ""; + $count = 0; + foreach ($exception->getTrace() as $frame) { + $args = ""; + if (isset($frame['args'])) { + $args = array(); + foreach ($frame['args'] as $arg) { + if (is_string($arg)) { + $args[] = "'" . $arg . "'"; + } elseif (is_array($arg)) { + $args[] = "Array"; + } elseif (is_null($arg)) { + $args[] = 'NULL'; + } elseif (is_bool($arg)) { + $args[] = ($arg) ? "true" : "false"; + } elseif (is_object($arg)) { + $args[] = get_class($arg); + } elseif (is_resource($arg)) { + $args[] = get_resource_type($arg); + } else { + $args[] = $arg; + } + } + $args = join(", ", $args); + } + $rtn .= sprintf( "#%s %s(%s): %s(%s)\n", + $count, + isset($frame['file']) ? $frame['file'] : 'unknown file', + isset($frame['line']) ? $frame['line'] : 'unknown line', + (isset($frame['class'])) ? $frame['class'].$frame['type'].$frame['function'] : $frame['function'], + $args ); + $count++; + } + return $rtn; + } + + static public function log_exception($ex) + { + $exceptions = array($ex); + while ($ex->getPrevious() !== null) { + $ex = $ex->getPrevious(); + $exceptions[] = $ex; + } + + foreach ($exceptions as $key => $e) { + $message = sprintf("Exception %d/%d '%s' with message '%s' in %s:%d\n", $key+1, count($exceptions), get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); + if (method_exists($e, "get_error_id")) { + $message .= 'Error ID: '.$e->get_error_id()."\n"; + } + if (method_exists($e, "get_data") && $e->get_data() !== NULL) { + $message .= 'Data: '.var_export($e->get_data(), true)."\n"; + } + $message .= "Backtrace:\n".self::getExceptionTraceAsString($e)."\n"; + error_log($message); + } + } + + // The actual exception handler + static public function exception_handler($ex) + { + self::log_exception($ex); + + $display_errors = in_array(strtolower(ini_get('display_errors')), array('1', 'on', 'true', 'stdout')); + if (php_sapi_name() === 'cli' OR defined('STDIN')) { + $display_errors = true; + } + + $GLOBALS["is_error_page"] = true; + $heading = "Internal Server Error"; + $message = "

An unhandled error occured.

\n"; + + if ($display_errors) { + $exceptions = array($ex); + while ($ex->getPrevious() !== null) { + $ex = $ex->getPrevious(); + $exceptions[] = $ex; + } + + foreach ($exceptions as $key => $e) { + $backtrace = self::getExceptionTraceAsString($e); + $message .= '
'; + $message .= 'Exception '.($key+1).' of '.count($exceptions).'
'; + $message .= 'Fatal error: Uncaught exception '.htmlspecialchars(get_class($e)).'
'; + $message .= 'Message: '.htmlspecialchars($e->getMessage()).'
'; + if (method_exists($e, "get_error_id")) { + $message .= 'Error ID: '.htmlspecialchars($e->get_error_id()).'
'; + } + if (method_exists($e, "get_data") && $e->get_data() !== NULL) { + $message .= 'Data:
'.htmlspecialchars(var_export($e->get_data(), true)).'

'; + } + $message .= 'Backtrace:
'; + $message .= '
'.htmlspecialchars(str_replace(FCPATH, "./", $backtrace)).'
'; + $message .= 'thrown in '.htmlspecialchars($e->getFile()).' on line '.htmlspecialchars($e->getLine()).'
'; + $message .= '
'; + } + } else { + $message .="

More information can be found in the php error log or by enabling display_errors.

"; + } + + $message = "$message"; + include FCPATH.APPPATH."/errors/error_general.php"; + } + + /** + * Checks for a fatal error, work around for set_error_handler not working on fatal errors. + */ + static public function check_for_fatal() + { + $error = error_get_last(); + if ($error["type"] == E_ERROR) { + self::exception_handler(new \ErrorException( + $error["message"], 0, $error["type"], $error["file"], $error["line"])); + } + } + + static public function assert_failure($file, $line, $expr, $message = "") + { + self::exception_handler(new Exception("assert($expr): Assertion failed in $file at line $line".($message != "" ? " with message: '$message'" : ""))); + exit(1); + } + +} diff --git a/index.php b/index.php index f9a9b66ce..0d75fb299 100644 --- a/index.php +++ b/index.php @@ -198,146 +198,10 @@ if (false && defined('ENVIRONMENT')) /* * Custom error handling */ -/* CI uses that name for it's error handling function. It misleading, but - * whatever. If I don't use it the framework will override my handler later. - */ -function _exception_handler($errno, $errstr, $errfile, $errline) -{ - if (!(error_reporting() & $errno)) { - // This error code is not included in error_reporting - return; - } - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); -} -set_error_handler("_exception_handler"); - -// Source: https://gist.github.com/abtris/1437966 -function getExceptionTraceAsString($exception) { - $rtn = ""; - $count = 0; - foreach ($exception->getTrace() as $frame) { - $args = ""; - if (isset($frame['args'])) { - $args = array(); - foreach ($frame['args'] as $arg) { - if (is_string($arg)) { - $args[] = "'" . $arg . "'"; - } elseif (is_array($arg)) { - $args[] = "Array"; - } elseif (is_null($arg)) { - $args[] = 'NULL'; - } elseif (is_bool($arg)) { - $args[] = ($arg) ? "true" : "false"; - } elseif (is_object($arg)) { - $args[] = get_class($arg); - } elseif (is_resource($arg)) { - $args[] = get_resource_type($arg); - } else { - $args[] = $arg; - } - } - $args = join(", ", $args); - } - $rtn .= sprintf( "#%s %s(%s): %s(%s)\n", - $count, - isset($frame['file']) ? $frame['file'] : 'unknown file', - isset($frame['line']) ? $frame['line'] : 'unknown line', - (isset($frame['class'])) ? $frame['class'].$frame['type'].$frame['function'] : $frame['function'], - $args ); - $count++; - } - return $rtn; -} - -function _log_exception($ex) -{ - $exceptions = array($ex); - while ($ex->getPrevious() !== null) { - $ex = $ex->getPrevious(); - $exceptions[] = $ex; - } - - foreach ($exceptions as $key => $e) { - $message = sprintf("Exception %d/%d '%s' with message '%s' in %s:%d\n", $key+1, count($exceptions), get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); - if (method_exists($e, "get_error_id")) { - $message .= 'Error ID: '.$e->get_error_id()."\n"; - } - if (method_exists($e, "get_data") && $e->get_data() !== NULL) { - $message .= 'Data: '.var_export($e->get_data(), true)."\n"; - } - $message .= "Backtrace:\n".getExceptionTraceAsString($e)."\n"; - error_log($message); - } -} - -// The actual exception handler -function _actual_exception_handler($ex) -{ - _log_exception($ex); - $display_errors = in_array(strtolower(ini_get('display_errors')), array('1', 'on', 'true', 'stdout')); - if (php_sapi_name() === 'cli' OR defined('STDIN')) { - $display_errors = true; - } - - $GLOBALS["is_error_page"] = true; - $heading = "Internal Server Error"; - $message = "

An unhandled error occured.

\n"; - - if ($display_errors) { - $exceptions = array($ex); - while ($ex->getPrevious() !== null) { - $ex = $ex->getPrevious(); - $exceptions[] = $ex; - } - - foreach ($exceptions as $key => $e) { - $backtrace = getExceptionTraceAsString($e); - $message .= '
'; - $message .= 'Exception '.($key+1).' of '.count($exceptions).'
'; - $message .= 'Fatal error: Uncaught exception '.htmlspecialchars(get_class($e)).'
'; - $message .= 'Message: '.htmlspecialchars($e->getMessage()).'
'; - if (method_exists($e, "get_error_id")) { - $message .= 'Error ID: '.htmlspecialchars($e->get_error_id()).'
'; - } - if (method_exists($e, "get_data") && $e->get_data() !== NULL) { - $message .= 'Data:
'.htmlspecialchars(var_export($e->get_data(), true)).'

'; - } - $message .= 'Backtrace:
'; - $message .= '
'.htmlspecialchars(str_replace(FCPATH, "./", $backtrace)).'
'; - $message .= 'thrown in '.htmlspecialchars($e->getFile()).' on line '.htmlspecialchars($e->getLine()).'
'; - $message .= '
'; - } - } else { - $message .="

More information can be found in the php error log or by enabling display_errors.

"; - } - - $message = "$message"; - include APPPATH."/errors/error_general.php"; -} -set_exception_handler('_actual_exception_handler'); - -/** - * Checks for a fatal error, work around for set_error_handler not working on fatal errors. - */ -function check_for_fatal() -{ - $error = error_get_last(); - if ($error["type"] == E_ERROR) { - _actual_exception_handler(new ErrorException( - $error["message"], 0, $error["type"], $error["file"], $error["line"])); - } -} -register_shutdown_function("check_for_fatal"); - -function _assert_failure($file, $line, $expr, $message = "") -{ - _actual_exception_handler(new Exception("assert($expr): Assertion failed in $file at line $line".($message != "" ? " with message: '$message'" : ""))); - exit(1); -} +require APPPATH.'libraries/ExceptionHandler.php'; +\libraries\ExceptionHandler::setup(); -assert_options(ASSERT_ACTIVE, true); -assert_options(ASSERT_CALLBACK, '_assert_failure'); /* * -------------------------------------------------------------------- * LOAD THE BOOTSTRAP FILE -- cgit v1.2.3-24-g4f1b