Created
January 28, 2011 17:05
-
-
Save evaisse/800563 to your computer and use it in GitHub Desktop.
A hardened error handler for PHP script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php # -*- coding:utf-8 -*- | |
| /** | |
| * handle exceptions, errors ( convert them into ErrorException instances) , | |
| * even fatal errors too via shutdown registrered function. | |
| * Provide a pretty smart debug pageon demand | |
| * | |
| * @example | |
| * register_shutdown_function('ErrorHandler::handleFatalError'); | |
| * ErrorHandler::setup(); | |
| * // only for PHP5.3 | |
| * ErrorHandler::registerEvent('handleException', function($error) { | |
| * mail('debug@admin.com', 'Error on','message' ); | |
| * }); | |
| * function myfunction() { | |
| * return true; | |
| * } | |
| * ErrorHandler::registerEvent('handleException', 'myfunction'); | |
| */ | |
| class ErrorHandler | |
| { | |
| static $debug = false; | |
| static $errnames = array( | |
| 1 => "E_ERROR", 2 => "E_WARNING", | |
| 4 => "E_PARSE", 8 => "E_NOTICE", | |
| 16 => "E_CORE_ERROR", 32 => "E_CORE_WARNING", | |
| 64 => "E_COMPILE_ERROR", 128 => "E_COMPILE_WARNING", | |
| 256 => "E_USER_ERROR", 512 => "E_USER_WARNING", | |
| 1024 => "E_USER_NOTICE", 2048 => "E_STRICT", | |
| 4096 => "E_RECOVERABLE_ERROR", 8191 => "E_ALL", | |
| 0 => "E_FATAL" ); | |
| protected static $isSet = false; | |
| protected static $events = array(); | |
| /** | |
| * set or call events | |
| * | |
| */ | |
| public static function registerEvent($name, $args=array()) | |
| { | |
| $eventName = (string)$eventName; | |
| if( !isset(self::$events[$eventName]) ) | |
| self::$events[$eventName] = array(); | |
| if( $args instanceof Closure ) { | |
| // set a closure as hook for event $eventName | |
| $events[$eventName][] = $args; | |
| } else { | |
| // set a classic string callback | |
| $events[$eventName][] = (string)$args; | |
| } | |
| return; | |
| } | |
| // ErrorHandler::event() | |
| /** | |
| * | |
| * @param string $eventName | |
| * @param array $args arguments to pass to | |
| * | |
| * @return mixed return event handler return value | |
| */ | |
| protected static function triggerEvent( $eventName, $args = array() ) | |
| { | |
| /* | |
| Call registered events | |
| */ | |
| if(!isset(self::$events[$eventName] )) return; | |
| foreach( self::$events[$eventName] as $event ) { | |
| call_user_func_array( $event, array() ); | |
| } | |
| } | |
| //- ErrorHandler::triggerEvent() | |
| /** | |
| * setup error handling environnement. | |
| * | |
| * @param int $errorLevel | |
| * | |
| * @return void | |
| */ | |
| public static function setup($errorLevel=0) | |
| { | |
| if(self::$isSet) return null; | |
| $errorLevel = !is_null($errorLevel) ? $errorLevel : error_reporting(); | |
| set_exception_handler( 'ErrorHandler::handle' ); | |
| set_error_handler( 'ErrorHandler::handle', error_reporting() ); | |
| register_shutdown_function( 'ErrorHandler::handle' ); | |
| } | |
| //- ErrorHandler::setup() | |
| /** | |
| * | |
| * | |
| */ | |
| public static function handle($e) | |
| { | |
| if( $event = self::triggerEvent(__FUNCTION__, func_get_args()) ) { | |
| return $event; | |
| } | |
| // use of @ before an expression must shutdown error handling | |
| if( error_reporting() == 0 ) | |
| return null; | |
| /* | |
| if the function is called as error handler, error properties are | |
| directly called as parameters. | |
| */ | |
| $e = func_get_args(); | |
| if( empty($e) ) { | |
| self::handleFatalError(); | |
| } elseif( count($e) == 1 ) { | |
| self::handleException($e[0]); | |
| } elseif( count($e) == 4 ) { | |
| self::handleError($e); | |
| } else { | |
| return null; | |
| } | |
| } | |
| //- ErrorHandler::handle | |
| /** | |
| * | |
| * | |
| * @param array $e error informations | |
| */ | |
| public static function handleError($e) | |
| { | |
| if( $event = self::triggerEvent(__FUNCTION__, func_get_args()) ) { | |
| return $event; | |
| } | |
| $e = new ErrorException( "[".self::$errnames[$e[0]]."] ".$e[1], | |
| 0, $e[0], $e[2], $e[3] ); | |
| self::handleException($e); | |
| } | |
| //- ErrorHandler::handleError() | |
| /** | |
| * check if there is a fatal error | |
| * | |
| * fatal error can be catched by setting a on shutdown | |
| * error controller | |
| * | |
| * @param mixed $e error parameters or nothing | |
| * @return void | |
| */ | |
| public static function handleFatalError() | |
| { | |
| if( $event = self::triggerEvent(__FUNCTION__, func_get_args()) ) { | |
| return $event; | |
| } | |
| /* | |
| no error, we are into test for fatal error ( shutdown function ) | |
| in this specific context, exception must not be throwned. | |
| So we call directly the exception handler to keep the application | |
| error handling logic. | |
| */ | |
| // check if there is a fatal error | |
| if( ( $e = error_get_last() ) | |
| // compare bit-wise to current error-reporting level | |
| and ( $e['type'] & error_reporting() ) | |
| ) { | |
| $e = array_values($e); | |
| $e = new ErrorException( "[FATAL_".self::$errnames[$e[0]]."] ".$e[1], | |
| 0, $e[0], $e[2], $e[3]); | |
| self::handleException($e); | |
| } | |
| } | |
| //- ErrorHandler:::handleFatalError() | |
| /** | |
| * handle exception and print error message | |
| * | |
| * @param object Exception $e | |
| * | |
| * @return void | |
| */ | |
| public static function handleException( Exception $e ) | |
| { | |
| if( $event = self::triggerEvent(__FUNCTION__, func_get_args()) ) { | |
| return $event; | |
| } | |
| ob_clean(); | |
| header('HTTP/1.0 500 Internal Server Error'); | |
| error_log($e); | |
| if(!self::$debug) return; | |
| $title = get_class($e); | |
| $tables = ''; | |
| $tables .= '<table class="datatable"> | |
| <thead><tr><th colspan=2>TRACE</th></tr></thead><tbody>'; | |
| foreach(array_reverse($e->getTrace()) as $k => $v) { | |
| $v['line'] = isset($v['line']) ? $v['line'] : '0'; | |
| $v['file'] = isset($v['file']) ? $v['file'] : '.'; | |
| $v['function'] = isset($v['function']) ? $v['function'] : '.'; | |
| $v['class'] = isset($v['class']) ? $v['class'] : '.'; | |
| $v['type'] = isset($v['type']) ? $v['type'] : '.'; | |
| $tables .= '<tr><th>'.html($v['class'].$v['type'].$v['function']) | |
| .'()</th>' | |
| .'<td>'.html(@$v['line'].':'.$v['file'],true) | |
| .'</td></tr>'; | |
| } | |
| $tables .= '</tbody></table>'; | |
| $debugs = array( 'GET' => &$_GET, 'POST' => &$_POST, | |
| 'COOKIE' => &$_COOKIE, 'SERVER' => &$_SERVER, | |
| 'FILES' => &$_FILES ); | |
| if(isset($_SESSION)) $debugs['SESSION'] = &$_SESSION; | |
| foreach($debugs as $name => $v) { | |
| $tables .= '<table class="datatable"> | |
| <thead><tr><th colspan=2>'.html($name).'</th></tr></thead><tbody>'; | |
| if(isset($v)) { | |
| foreach($v as $key => $val) { | |
| $val = (is_array($val) or is_object($val)) | |
| ? print_r($val,1) | |
| : (string)$val; | |
| $tables .= '<tr><th>'.html($key).'</th>' | |
| .'<td>'.html($val).'</td></tr>'; | |
| } | |
| } | |
| $tables .= '</tbody></table>'; | |
| } | |
| print ' | |
| <html> | |
| <head> | |
| <title>'.$title.'</title> | |
| <style> | |
| .datatable { width:100%;font-family:monospace;margin:10px 0px 5px; | |
| border:1px solid #CCC;border-collapse:collapse; } | |
| .datatable thead { background:#DDD;color:#444 } | |
| .datatable th,td {border:1px solid #CCC;padding:4px 5px;text-align:left} | |
| .datatable tbody th {text-align:right;width:200px} | |
| .datatable tbody tr:nth-child(even) {background: #EEE} | |
| .message {background-color:#EEE;border:1px solid #CCC;padding:12px} | |
| .message pre { background-color:#555;color:#DDD;padding:9px;margin:5px;} | |
| </style> | |
| </head> | |
| <body> | |
| <h2 style="color:red;margin:0px 0px 10px;background-color:#FCC; | |
| border:1px solid #DAA;padding:12px;">'.$title.'</h2> | |
| <div style="padding:10px;margin:10px 0px; | |
| border:1px solid #C00;color:#C00">'.$e->getMessage().'</div> | |
| <div style="padding:10px;margin:10px 0px; | |
| color:#555;border:1px solid #CCC"> | |
| Line <tt>'.$e->getLine().'</tt> in <tt>'.$e->getFile().'</tt> | |
| </div>'.$tables.' | |
| </body> | |
| </html>'; | |
| exit(); | |
| } | |
| //- ErrorHandler::handleException() | |
| } | |
| //- ErrorHandler |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment