Location: PHPKode > scripts > PHP ErrorHandler Beta > php-errorhandler-beta/ErrorHandler.class.php
<?php
/**
 * @author Michael Grosser - grosser.michael-AT-gmail.com
 */

require_once('Error.class.php');
require_once('ErrorReportingStatus.class.php');

class ErrorHandler {
	//--------instance
	protected static $instance;

	/**
	 * @return ErrorHandler
	 */
	public static function get_instance(){
		if(! isset(self::$instance)){
			self::$instance = new self();
		}
		return self::$instance;
	}
	
	private function __construct(){	}

	private function __clone(){}
	
	//--------END instance

	//--------LISTENERS
	private $listeners = array();
	
	/**
	 * @return array
	 */
	public function get_listeners(){
		return $this->listeners;
	}
	
	/**
	 * @param ErrorProcessor
	 */
	public function add_listener(ErrorProcessor $processor){
		$this->listeners[] = $processor;
	}
	
	public function remove_listeners(){
		$this->listeners = array();
	}
	//--------END LISTENERS

	/**
	 * @param $level -- set error level
	 */
	public function activate($level=-1){
		$this->save_state();
		
		$this->activate_assert();
		$this->activate_error_reporting($level);
	}
	
	public function deactivate(){
		$this->restore_state();
	}
	
	//--------STATE
	/**
	 * @var ErrorReportingStatus
	 */
	private $saved_state;
	
	/**
	 * @return bool succsess
	 */
	private function save_state(){
		if(isset($this->saved_state)) return false;
		$this->saved_state = new ErrorReportingStatus();
		return true;
	}
	
	/**
	 * @return bool succsess
	 */
	private function restore_state(){
		if(!isset($this->saved_state)) return false;
		$this->saved_state->restore();
		return true;
	}
	//--------END STATE

	#####################################################################################
	# Assertions
	#####################################################################################
	public function activate_assert(){
		assert_options(ASSERT_ACTIVE, 1);
		assert_options(ASSERT_WARNING, 0);
		assert_options(ASSERT_BAIL, 1);//exit after false assert
		assert_options(ASSERT_CALLBACK, 'assert_callback');
//		assert_options(ASSERT_QUIET_EVAL, 0);
	}

	/**
	 * Assertion failed...
	 *
	 * @param String $script
	 * @param Int $line
	 * @param String $message
	 */
	public function assert_callback($message){
		assert(is_string($message));
		$message = "Condition: $message failed!";
		
		$this->notify_listeners(Error::ASSERT_TYPE,$message);
	}

	#####################################################################################
	# Errorhandling
	#####################################################################################
	/**
	 *@param Int $level -- see error_reporting levels php
	 */
	public function activate_error_reporting($level=-1){
		assert(is_numeric($level));
		if($level != -1){
			//no fatal errors shown when level does not contain E_ERROR
			$level |= E_ERROR;
			error_reporting($level);
		}
		set_error_handler("error_callback");
	}
	
	//--------error_handling
	const HANDLING_ONCE = 'once';				//process once, ignore all afterward
	const HANDLING_MULTIPLE = 'multiple';	//process all that comes
	const HANDLING_BAIL = 'bail';				//exit after processing first error
	
	private $error_handling_type = self::HANDLING_MULTIPLE;
	private $error_handled = false;
	
	public function error_handling($type){
		assert(
			$type==self::HANDLING_BAIL ||
			$type==self::HANDLING_ONCE ||
			$type==self::HANDLING_MULTIPLE
		);
		$this->error_handling_type = $type;
	}
	//--------END error_bail

	public function error_callback($type,$message,$file_name){
		assert(is_numeric($type));
		assert(is_string($message));
		assert(is_string($file_name));
		
		if($this->error_handling_type == self::HANDLING_ONCE){
			if($this->error_handled)return;
			else	$this->error_handled = true;
		}
		
		if($this->error_should_be_ignored($type,$file_name))return;
		$this->notify_listeners($type,$message);
		
		if($this->error_handling_type == self::HANDLING_BAIL)exit;
	}
	
	private function notify_listeners($error_type,$message){
		$backtrace = $this->build_backtrace();
		$error = new Error($error_type,$message,$backtrace);

		foreach($this->get_listeners() as $listener){
			$listener->notify_of_error($error);
		}
	}

	private function build_backtrace(){
		$backtrace = debug_backtrace();
		//filter anything after/including error_callback
		$kill = false;
		foreach($backtrace as $key => $part){
			if( empty($part['class']) && ($part['function']==='error_callback' || $part['function']==='assert_callback')){
				//cleanup global vars...
				if($part['function']==='error_callback'){
					//remove the args & function from the error_handler_call
					unset($backtrace[0]['args']); //NEVER REMOVE!!!! -> recursion!
					unset($backtrace[0]['function']);
				}
				else {//assertion we dont need anything from this line...
					array_shift($backtrace);//NEVER REMOVE!!!! -> recursion!
				}
				break;
			}
			array_shift($backtrace);
		}
		
		if(empty($backtrace[0])){
			array_shift($backtrace);
		}

		return $backtrace;
	}


	#####################################################################################
	# ERROR IGNORING
	#####################################################################################
	
	const IGNORE_WHITELIST = 'whitelist';
	const IGNORE_BLACKLIST = 'blacklist';
	/**
	 * 
	 * @param $ignore_type String IGNORE_WHITELIST or IGNORE_BLACKLIST
	 * @param $list array directory-parts to ignore as source of error
	 * 	array(
	 * 		'simpletest' => E_NOTICE | E_STRICT,
	 * 	);
	 *  
	 */
	public function ignore_errors(array $list,$ignore_type=self::IGNORE_BLACKLIST){
		assert(
			$ignore_type==self::IGNORE_WHITELIST || 
			$ignore_type==self::IGNORE_BLACKLIST
		);
		foreach($list as $key => $value){
			//string instead of error-type as value
			if(!is_numeric($value)){
				assert(is_numeric($key));
				unset($list[$key]);
				$list[$value]=E_ALL|E_STRICT;
			}
		}
		
		$this->error_ignore_type = $ignore_type;
		$this->error_ignore_list = $list;
	}
	
	private $error_ignore_type = self::IGNORE_BLACKLIST; 
	private $error_ignore_list;//never call direct
	
	private function get_error_ignore_list(){
		if(isset($this->error_ignore_list))return $this->error_ignore_list;

		//default (cannot be set since calculations( | ) are not allowed as defaults)
		return array(
			'/usr/share/php/'=>E_NOTICE | E_STRICT,
			'simpletest/'=>E_NOTICE | E_STRICT,
			'/xorc/'=>E_NOTICE | E_STRICT,
		);
	}
	
	/**
	 * @param Int $type
	 * @Todo goes to Error Processor
	 */
	private function error_should_be_ignored($error_type,$file_name){
		if($this->error_type_is_ignored($error_type))return true;
		if($this->is_bailed_assertion($error_type))return false; 
		if($this->file_is_ignored_from_error_handling($file_name,$error_type))return true;
	}
	
	private function error_type_is_ignored($error_type){
		if ($this->bit_is_set($error_type,error_reporting())) return false;
		return true;
	}
	
	/**
	 * $bit in $int
	 * @param $bit int 1 2 4 8
	 * @param $int int 1 2 3 4
	 */
	private function bit_is_set($bit,$int){
		return ($bit | $int) == $int;
	}
	
	private function is_bailed_assertion($type){
		if($type == Error::ASSERT_TYPE && assert_options(ASSERT_BAIL))return true;
		return false;
	}
	
	/**
	 * @param String $file_path
	 */
	private function file_is_ignored_from_error_handling($file_path,$error_type){
		//anything on the blacklist is ignored
		if($this->error_ignore_type == self::IGNORE_BLACKLIST){
			foreach($this->get_error_ignore_list() as $word => $ignored_errors){
				$error_ignored = $this->bit_is_set($error_type,$ignored_errors);
				if($this->word_in_path($word,$file_path) && $error_ignored){
					return true;
				}
			}
		}
		//anything NOT on the whitelist is ignored
		else if($this->error_ignore_type == self::IGNORE_WHITELIST){
			foreach($this->get_error_ignore_list() as $word => $allowed_errors){
				$error_allowed = $this->bit_is_set($error_type,$allowed_errors);
				if($this->word_in_path($word,$file_path) && $error_allowed){
					return false;
				}
			}
			return true;
		}
		else{
			throw new Exception('error_ignore_type is wrong');
		}
		return false;
	}
	
	/**
	 * word is in path ? (in-casesensitive)
	 */
	private function word_in_path($word,$path){
		assert($word && $path);
		return is_int(strpos(strtolower($path),strtolower($word)));
	}
}

/**
 * Called when error occures
 *
 * @param String $script_path
 * @param Int $line
 * @param String $message
 */
function assert_callback($script_path=null, $line=null, $message) {
	$script_path;$line;//we dont need sice we use backtrace! ->no compiler warnings
	
	global $debugger_recursion;
	if(!$debugger_recursion){
		$debugger_recursion = true;
		ErrorHandler::get_instance()->assert_callback($message);
		$debugger_recursion = false;
	}
	else {
		print "ASSERTION in ERROR HANDLER FAILED! $script_path $line $message";
	}

}

function error_callback($type, $message, $file, $line=null, $context=null){
	// don't respond to the error if it
	// was suppressed with a '@' -> error_reporting() = 0
	if(error_reporting()===0)return;
	
	$line;$context;//we dont need sice we use backtrace! ->no compiler warnings
	global $debugger_recursion;
	if(!$debugger_recursion){
		$debugger_recursion = true;
		ErrorHandler::get_instance()->error_callback($type,$message,$file);
		$debugger_recursion = false;
	}
	else {
		print "ERROR in ERROR HANDLER! $type $msg $file $line $contect";
	}
}

?>
Return current item: PHP ErrorHandler Beta