A PHP error and exception handling class that displays very well
the implementation code
<? php
// custom exception function
set_exception_handler ('handle_exception');
// custom error function
set_error_handler ('handle_error');
/ **
* Exception Handling
*
* @param mixed $ exception exception object
* @author blog.snsgou.com
* /
function handle_exception ($ exception) {
Error :: exceptionError ($ exception);
}
/ **
* Error handling
*
* @param string $ errNo error code
* @param string $ errStr error message
* @param string $ errFile error file
* @param string $ errLine error line
* @author blog.snsgou.com
* /
function handle_error ($ errNo, $ errStr, $ errFile, $ errLine) {
if ($ errNo) {
Error :: systemError ($ errStr, false, true, false);
}
}
/ **
* System error handling
*
* @author blog.snsgou.com
* /
class Error {
public static function systemError ($ message, $ show = true, $ save = true, $ halt = true) {
list ($ showTrace, $ logTrace) = self :: debugBacktrace ();
if ($ save) {
$ messageSave = '<b>'. $ message. '</ b> <br /> <b> PHP: </ b>'. $ logTrace;
self :: writeErrorLog ($ messageSave);
}
if ($ show) {
self :: showError ('system', "<li> $ message </ li>", $ showTrace, 0);
}
if ($ halt) {
exit ();
} else {
return $ message;
}
}
/ **
* Traceback information during code execution
*
* @static
* @access public
* /
public static function debugBacktrace () {
$ skipFunc [] = 'Error-> debugBacktrace';
$ show = $ log = '';
$ debugBacktrace = debug_backtrace ();
ksort ($ debugBacktrace);
foreach ($ debugBacktrace as $ k => $ error) {
if (! isset ($ error ['file'])) {
// Use reflection API to get the number of files and lines where the method / function is
try {
if (isset ($ error ['class'])) {
$ reflection = new ReflectionMethod ($ error ['class'], $ error ['function']);
} else {
$ reflection = new ReflectionFunction ($ error ['function']);
}
$ error ['file'] = $ reflection-> getFileName ();
$ error ['line'] = $ reflection-> getStartLine ();
} catch (Exception $ e) {
continue;
}
}
$ file = str_replace (SITE_PATH, '', $ error ['file']);
$ func = isset ($ error ['class'])? $ error ['class']: '';
$ func. = isset ($ error ['type'])? $ error ['type']: '';
$ func. = isset ($ error ['function'])? $ error ['function']: '';
if (in_array ($ func, $ skipFunc)) {
break;
}
$ error ['line'] = sprintf ('% 04d', $ error ['line']);
$ show. = '<li> [Line:'. $ error ['line']. ']'. $ file. '('. $ func. ') </ li>';
$ log. =! empty ($ log)? '->': '';
$ log. = $ file. ':'. $ error ['line'];
}
return array ($ show, $ log);
}
/ **
* Exception Handling
*
* @static
* @access public
* @param mixed $ exception
* /
public static function exceptionError ($ exception) {
if ($ exception instanceof DbException) {
$ type = 'db';
} else {
$ type = 'system';
}
if ($ type == 'db') {
$ errorMsg = '('. $ exception-> getCode (). ')';
$ errorMsg. = self :: sqlClear ($ exception-> getMessage (), $ exception-> getDbConfig ());
if ($ exception-> getSql ()) {
$ errorMsg. = '<div class = "sql">';
$ errorMsg. = self :: sqlClear ($ exception-> getSql (), $ exception-> getDbConfig ());
$ errorMsg. = '</ div>';
}
} else {
$ errorMsg = $ exception-> getMessage ();
}
$ trace = $ exception-> getTrace ();
krsort ($ trace);
$ trace [] = array ('file' => $ exception-> getFile (), 'line' => $ exception-> getLine (), 'function' => 'break');
$ phpMsg = array ();
foreach ($ trace as $ error) {
if (! empty ($ error ['function'])) {
$ fun = '';
if (! empty ($ error ['class'])) {
$ fun. = $ error ['class']. $ error ['type'];
}
$ fun. = $ error ['function']. '(';
if (! empty ($ error ['args'])) {
$ mark = '';
foreach ($ error ['args'] as $ arg) {
$ fun. = $ mark;
if (is_array ($ arg)) {
$ fun. = 'Array';
} elseif (is_bool ($ arg)) {
$ fun. = $ arg? 'true': 'false';
} elseif (is_int ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% d';
} elseif (is_float ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% f';
} else {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? '\' '. htmlspecialchars (substr (self :: clear ($ arg), 0, 10)). (strlen ($ arg)> 10?'. .. ':' ').' \ '': '% s';
}
$ mark = ',';
}
}
$ fun. = ')';
$ error ['function'] = $ fun;
}
if (! isset ($ error ['line'])) {
continue;
}
$ phpMsg [] = array ('file' => str_replace (array (SITE_PATH, '\\'), array ('', '/'), $ error ['file']), 'line' => $ error ['line'], 'function' => $ error ['function']);
}
self :: showError ($ type, $ errorMsg, $ phpMsg);
exit ();
}
/ **
* Record error log
*
* @static
* @access public
* @param string $ message
* /
public static function writeErrorLog ($ message) {
return false; // do not write temporarily
$ message = self :: clear ($ message);
$ time = time ();
$ file = LOG_PATH. '/'. date ('Y.m.d'). '_errorlog.php';
$ hash = md5 ($ message);
$ userId = 0;
$ ip = get_client_ip ();
$ user = '<b> User: </ b> userId ='. intval ($ userId). '; IP ='. $ ip. '; RIP:'. $ _SERVER ['REMOTE_ADDR'];
$ uri = 'Request:'. htmlspecialchars (self :: clear ($ _ SERVER ['REQUEST_URI']));
$ message = "<? php exit;?> \ t {$ time} \ t $ message \ t $ hash \ t $ user $ uri \ n";
// Determine whether the $ message has been recorded within the time interval $ maxtime. If yes, no further recording is required.
if (is_file ($ file)) {
$ fp = @fopen (<? php
// custom exception function
set_exception_handler ('handle_exception');
// custom error function
set_error_handler ('handle_error');
/ **
* Exception Handling
*
* @param mixed $ exception exception object
* @author blog.snsgou.com
* /
function handle_exception ($ exception) {
Error :: exceptionError ($ exception);
}
/ **
* Error handling
*
* @param string $ errNo error code
* @param string $ errStr error message
* @param string $ errFile error file
* @param string $ errLine error line
* @author blog.snsgou.com
* /
function handle_error ($ errNo, $ errStr, $ errFile, $ errLine) {
if ($ errNo) {
Error :: systemError ($ errStr, false, true, false);
}
}
/ **
* System error handling
*
* @author blog.snsgou.com
* /
class Error {
public static function systemError ($ message, $ show = true, $ save = true, $ halt = true) {
list ($ showTrace, $ logTrace) = self :: debugBacktrace ();
if ($ save) {
$ messageSave = '<b>'. $ message. '</ b> <br /> <b> PHP: </ b>'. $ logTrace;
self :: writeErrorLog ($ messageSave);
}
if ($ show) {
self :: showError ('system', "<li> $ message </ li>", $ showTrace, 0);
}
if ($ halt) {
exit ();
} else {
return $ message;
}
}
/ **
* Traceback information during code execution
*
* @static
* @access public
* /
public static function debugBacktrace () {
$ skipFunc [] = 'Error-> debugBacktrace';
$ show = $ log = '';
$ debugBacktrace = debug_backtrace ();
ksort ($ debugBacktrace);
foreach ($ debugBacktrace as $ k => $ error) {
if (! isset ($ error ['file'])) {
// Use reflection API to get the number of files and lines where the method / function is
try {
if (isset ($ error ['class'])) {
$ reflection = new ReflectionMethod ($ error ['class'], $ error ['function']);
} else {
$ reflection = new ReflectionFunction ($ error ['function']);
}
$ error ['file'] = $ reflection-> getFileName ();
$ error ['line'] = $ reflection-> getStartLine ();
} catch (Exception $ e) {
continue;
}
}
$ file = str_replace (SITE_PATH, '', $ error ['file']);
$ func = isset ($ error ['class'])? $ error ['class']: '';
$ func. = isset ($ error ['type'])? $ error ['type']: '';
$ func. = isset ($ error ['function'])? $ error ['function']: '';
if (in_array ($ func, $ skipFunc)) {
break;
}
$ error ['line'] = sprintf ('% 04d', $ error ['line']);
$ show. = '<li> [Line:'. $ error ['line']. ']'. $ file. '('. $ func. ') </ li>';
$ log. =! empty ($ log)? '->': '';
$ log. = $ file. ':'. $ error ['line'];
}
return array ($ show, $ log);
}
/ **
* Exception Handling
*
* @static
* @access public
* @param mixed $ exception
* /
public static function exceptionError ($ exception) {
if ($ exception instanceof DbException) {
$ type = 'db';
} else {
$ type = 'system';
}
if ($ type == 'db') {
$ errorMsg = '('. $ exception-> getCode (). ')';
$ errorMsg. = self :: sqlClear ($ exception-> getMessage (), $ exception-> getDbConfig ());
if ($ exception-> getSql ()) {
$ errorMsg. = '<div class = "sql">';
$ errorMsg. = self :: sqlClear ($ exception-> getSql (), $ exception-> getDbConfig ());
$ errorMsg. = '</ div>';
}
} else {
$ errorMsg = $ exception-> getMessage ();
}
$ trace = $ exception-> getTrace ();
krsort ($ trace);
$ trace [] = array ('file' => $ exception-> getFile (), 'line' => $ exception-> getLine (), 'function' => 'break');
$ phpMsg = array ();
foreach ($ trace as $ error) {
if (! empty ($ error ['function'])) {
$ fun = '';
if (! empty ($ error ['class'])) {
$ fun. = $ error ['class']. $ error ['type'];
}
$ fun. = $ error ['function']. '(';
if (! empty ($ error ['args'])) {
$ mark = '';
foreach ($ error ['args'] as $ arg) {
$ fun. = $ mark;
if (is_array ($ arg)) {
$ fun. = 'Array';
} elseif (is_bool ($ arg)) {
$ fun. = $ arg? 'true': 'false';
} elseif (is_int ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% d';
} elseif (is_float ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% f';
} else {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? '\' '. htmlspecialchars (substr (self :: clear ($ arg), 0, 10)). (strlen ($ arg)> 10?'. .. ':' ').' \ '': '% s';
}
$ mark = ',';
}
}
$ fun. = ')';
$ error ['function'] = $ fun;
}
if (! isset ($ error ['line'])) {
continue;
}
$ phpMsg [] = array ('file' => str_replace (array (SITE_PATH, '\\'), array ('', '/'), $ error ['file']), 'line' => $ error ['line'], 'function' => $ error ['function']);
}
self :: showError ($ type, $ errorMsg, $ phpMsg);
exit ();
}
/ **
* Record error log
*
* @static
* @access public
* @param string $ message
* /
public static function writeErrorLog ($ message) {
return false; // do not write temporarily
$ message = self :: clear ($ message);
$ time = time ();
$ file = LOG_PATH. '/'. date ('Y.m.d'). '_errorlog.php';
$ hash = md5 ($ message);
$ userId = 0;
$ ip = get_client_ip ();
$ user = '<b> User: </ b> userId ='. intval ($ userId). '; IP ='. $ ip. '; RIP:'. $ _SERVER ['REMOTE_ADDR'];
$ uri = 'Request:'. htmlspecialchars (self :: clear ($ _ SERVER ['REQUEST_URI']));
$ message = "<? php exit;?> \ t {$ time} \ t $ message \ t $ hash \ t $ user $ uri \ n";
// Determine whether the $ message has been recorded within the time interval $ maxtime. If yes, no further recording is required.
if (is_file ($ file)) {
$ fp = @fopen (<? php
// custom exception function
set_exception_handler ('handle_exception');
// custom error function
set_error_handler ('handle_error');
/ **
* Exception Handling
*
* @param mixed $ exception exception object
* @author blog.snsgou.com
* /
function handle_exception ($ exception) {
Error :: exceptionError ($ exception);
}
/ **
* Error handling
*
* @param string $ errNo error code
* @param string $ errStr error message
* @param string $ errFile error file
* @param string $ errLine error line
* @author blog.snsgou.com
* /
function handle_error ($ errNo, $ errStr, $ errFile, $ errLine) {
if ($ errNo) {
Error :: systemError ($ errStr, false, true, false);
}
}
/ **
* System error handling
*
* @author blog.snsgou.com
* /
class Error {
public static function systemError ($ message, $ show = true, $ save = true, $ halt = true) {
list ($ showTrace, $ logTrace) = self :: debugBacktrace ();
if ($ save) {
$ messageSave = '<b>'. $ message. '</ b> <br /> <b> PHP: </ b>'. $ logTrace;
self :: writeErrorLog ($ messageSave);
}
if ($ show) {
self :: showError ('system', "<li> $ message </ li>", $ showTrace, 0);
}
if ($ halt) {
exit ();
} else {
return $ message;
}
}
/ **
* Traceback information during code execution
*
* @static
* @access public
* /
public static function debugBacktrace () {
$ skipFunc [] = 'Error-> debugBacktrace';
$ show = $ log = '';
$ debugBacktrace = debug_backtrace ();
ksort ($ debugBacktrace);
foreach ($ debugBacktrace as $ k => $ error) {
if (! isset ($ error ['file'])) {
// Use reflection API to get the number of files and lines where the method / function is
try {
if (isset ($ error ['class'])) {
$ reflection = new ReflectionMethod ($ error ['class'], $ error ['function']);
} else {
$ reflection = new ReflectionFunction ($ error ['function']);
}
$ error ['file'] = $ reflection-> getFileName ();
$ error ['line'] = $ reflection-> getStartLine ();
} catch (Exception $ e) {
continue;
}
}
$ file = str_replace (SITE_PATH, '', $ error ['file']);
$ func = isset ($ error ['class'])? $ error ['class']: '';
$ func. = isset ($ error ['type'])? $ error ['type']: '';
$ func. = isset ($ error ['function'])? $ error ['function']: '';
if (in_array ($ func, $ skipFunc)) {
break;
}
$ error ['line'] = sprintf ('% 04d', $ error ['line']);
$ show. = '<li> [Line:'. $ error ['line']. ']'. $ file. '('. $ func. ') </ li>';
$ log. =! empty ($ log)? '->': '';
$ log. = $ file. ':'. $ error ['line'];
}
return array ($ show, $ log);
}
/ **
* Exception Handling
*
* @static
* @access public
* @param mixed $ exception
* /
public static function exceptionError ($ exception) {
if ($ exception instanceof DbException) {
$ type = 'db';
} else {
$ type = 'system';
}
if ($ type == 'db') {
$ errorMsg = '('. $ exception-> getCode (). ')';
$ errorMsg. = self :: sqlClear ($ exception-> getMessage (), $ exception-> getDbConfig ());
if ($ exception-> getSql ()) {
$ errorMsg. = '<div class = "sql">';
$ errorMsg. = self :: sqlClear ($ exception-> getSql (), $ exception-> getDbConfig ());
$ errorMsg. = '</ div>';
}
} else {
$ errorMsg = $ exception-> getMessage ();
}
$ trace = $ exception-> getTrace ();
krsort ($ trace);
$ trace [] = array ('file' => $ exception-> getFile (), 'line' => $ exception-> getLine (), 'function' => 'break');
$ phpMsg = array ();
foreach ($ trace as $ error) {
if (! empty ($ error ['function'])) {
$ fun = '';
if (! empty ($ error ['class'])) {
$ fun. = $ error ['class']. $ error ['type'];
}
$ fun. = $ error ['function']. '(';
if (! empty ($ error ['args'])) {
$ mark = '';
foreach ($ error ['args'] as $ arg) {
$ fun. = $ mark;
if (is_array ($ arg)) {
$ fun. = 'Array';
} elseif (is_bool ($ arg)) {
$ fun. = $ arg? 'true': 'false';
} elseif (is_int ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% d';
} elseif (is_float ($ arg)) {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? $ arg: '% f';
} else {
$ fun. = (defined ('SITE_DEBUG') && SITE_DEBUG)? '\' '. htmlspecialchars (substr (self :: clear ($ arg), 0, 10)). (strlen ($ arg)> 10?'. .. ':' ').' \ '': '% s';
}
$ mark = ',';
}
}
$ fun. = ')';
$ error ['function'] = $ fun;
}
if (! isset ($ error ['line'])) {
continue;
}
$ phpMsg [] = array ('file' => str_replace (array (SITE_PATH, '\\'), array ('', '/'), $ error ['file']), 'line' => $ error ['line'], 'function' => $ error ['function']);
}
self :: showError ($ type, $ errorMsg, $ phpMsg);
exit ();
}
/ **
* Record error log
*
* @static
* @access public
* @param string $ message
* /
public static function writeErrorLog ($ message) {
return false; // do not write temporarily
$ message = self :: clear ($ message);
$ time = time ();
$ file = LOG_PATH. '/'. date ('Y.m.d'). '_errorlog.php';
$ hash = md5 ($ message);
$ userId = 0;
$ ip = get_client_ip ();
$ user = '<b> User: </ b> userId ='. intval ($ userId). '; IP ='. $ ip. '; RIP:'. $ _SERVER ['REMOTE_ADDR'];
$ uri = 'Request:'. htmlspecialchars (self :: clear ($ _ SERVER ['REQUEST_URI']));
$ message = "<? php exit;?> \ t {$ time} \ t $ message \ t $ hash \ t $ user $ uri \ n";
// Determine whether the $ message has been recorded within the time interval $ maxtime. If yes, no further recording is required.
if (is_file ($ file)) {
$ fp = @fopen ($ file, 'rb');
$ lastlen = 50000; // read the last $ lastlen length byte content
$ maxtime = 60 * 10; // time interval: 10 minutes
$ offset = filesize ($ file)-$ lastlen;
if ($ offset> 0) {
fseek ($ fp, $ offset);
}
if ($ data = fread ($ fp, $ lastlen)) {
$ array = explode ("\ n", $ data);
if (is_array ($ array))
foreach ($ array as $ key => $ val) {
$ row = explode ("\ t", $ val);
if ($ row [0]! = '<? php exit;?>') {
continue;
}
if ($ row [3] == $ hash && ($ row [1]> $ time-$ maxtime)) {
return;
}
}
}
}
error_log ($ message, 3, $ file);
}
/ **
* Clear text characters
*
* @param string $ message
* /
public static function clear ($ message) {
return str_replace (array ("\ t", "\ r", "\ n"), "", $ message);
}
/ **
* SQL statement character cleanup
*
* @static
* @access public
* @param string $ message
* @param string $ dbConfig
* /
public static function sqlClear ($ message, $ dbConfig) {
$ message = self :: clear ($ message);
if (! (defined ('SITE_DEBUG') && SITE_DEBUG)) {
$ message = str_replace ($ dbConfig ['database'], '***', $ message);
// $ message = str_replace ($ dbConfig ['prefix'], '***', $ message);
$ message = str_replace (C ('DB_PREFIX'), '***', $ message);
}
$ message = htmlspecialchars ($ message);
return $ message;
}
/ **
* Display error
*
* @static
* @access public
* @param string $ type error type db, system
* @param string $ errorMsg
* @param string $ phpMsg
* /
public static function showError ($ type, $ errorMsg, $ phpMsg = '') {
global $ _G;
$ errorMsg = str_replace (SITE_PATH, '', $ errorMsg);
ob_end_clean ();
$ host = $ _SERVER ['HTTP_HOST'];
$ title = $ type == 'db'? 'Database': 'System';
echo <<< EOT
<! DOCTYPE html PUBLIC "-// W3C // DTD XHTML 1.0 Transitional // EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title> $ host-$ title Error </ title>
<meta http-equiv = "Content-Type" content = "text / html; charset = {$ _ G ['config'] ['output'] ['charset']}" />
<meta name = "ROBOTS" content = "NOINDEX, NOFOLLOW, NOARCHIVE" />
<style type = "text / css">
<!-
body {background-color: white; color: black; font: 9pt / 11pt verdana, arial, sans-serif;}
#container {margin: 10px;}
#message {width: 1024px; color: black;}
.red {color: red;}
a: link {font: 9pt / 11pt verdana, arial, sans-serif; color: red;}
a: visited {font: 9pt / 11pt verdana, arial, sans-serif; color: # 4e4e4e;}
h1 {color: # FF0000; font: 18pt "Verdana"; margin-bottom: 0.5em;}
.bg1 {background-color: #FFFFCC;}
.bg2 {background-color: #EEEEEE;}
.table {background: #AAAAAA; font: 11pt Menlo, Consolas, "Lucida Console"}
.info {
background: none repeat scroll 0 0 # F3F3F3;
border: 0px solid #aaaaaa;
border-radius: 10px 10px 10px 10px;
color: # 000000;
font-size: 11pt;
line-height: 160%;
margin-bottom: 1em;
padding: 1em;
}
.help {
background: # F3F3F3;
border-radius: 10px 10px 10px 10px;
font: 12px verdana, arial, sans-serif;
text-align: center;
line-height: 160%;
padding: 1em;
}
.sql {
background: none repeat scroll 0 0 #FFFFCC;
border: 1px solid #aaaaaa;
color: # 000000;
font: arial, sans-serif;
font-size: 9pt;
line-height: 160%;
margin-top: 1em;
padding: 4px;
}
->
</ style>
</ head>
<body>
<div id = "container">
<h1> $ title Error </ h1>
<div class = 'info'> $ errorMsg </ div>
EOT;
if (! empty ($ phpMsg)) {
echo '<div class = "info">';
echo '<p> <strong> PHP Debug </ strong> </ p>';
echo '<table cellpadding = "5" cellspacing = "1" width = "100%" class = "table"> <tbody>';
if (is_array ($ phpMsg)) {
echo '<tr class = "bg2"> <td> No. </ td> <td> File </ td> <td> Line </ td> <td> Code </ td> </ tr>';
foreach ($ phpMsg as $ k => $ msg) {
$ k ++;
echo '<tr class = "bg1">';
echo '<td>'. $ k. '</ td>';
echo '<td>'. $ msg ['file']. '</ td>';
echo '<td>'. $ msg ['line']. '</ td>';
echo '<td>'. $ msg ['function']. '</ td>';
echo '</ tr>';
}
} else {
echo '<tr> <td> <ul>'. $ phpMsg. '</ ul> </ td> </ tr>';
}
echo '</ tbody> </ table> </ div>';
}
echo <<< EOT
</ div>
</ body>
</ html>
EOT;
exit ();
}
}
/ **
* DB exception class
*
* @author blog.snsgou.com
* /
class DbException extends Exception {
protected $ sql;
protected $ dbConfig; // current database configuration information
public function __construct ($ message, $ code = 0, $ sql = '', $ dbConfig = array ()) {
$ this-> sql = $ sql;
$ this-> dbConfig = $ dbConfig;
parent :: __ construct ($ message, $ code);
}
public function getSql () {
return $ this-> sql;
}
public function getDbConfig () {
return $ this-> dbConfig;
}
}