New and improved error handling class!

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Daedalus- wrote:You guys succeeded in doing something that hundreds of tutorials and web pages could not do:

You made me understand a design pattern. :p
Glad you learned something new :)
jamiel
Forum Contributor
Posts: 276
Joined: Wed Feb 22, 2006 5:17 am
Location: London, United Kingdom

Post by jamiel »

Back to the original topic, We use Exceptions for most of our error handling ... so use a Log_Exception, Mail_Exception, STD_Exception etc. which all extend Exception.
User avatar
daedalus__
DevNet Resident
Posts: 1925
Joined: Thu Feb 09, 2006 4:52 pm

Post by daedalus__ »

try...catch stuff?

i haven't figured it out
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

try + catch is simple, and is my preference as you are raising individual exceptions, over categorised errors (e.g. E_USER_ERROR etc.)

Code: Select all

class DummyException extends Exception {}

class MyClass
{
    public function __construct ()
    {
        throw new DummyException ('This is a dummy Exception!');
    }
}

try {
    $foo = new MyClass;
} catch (DummyException $e) {
    echo 'DummyException caught.. continuing..';
} catch (Exception $e) {
    echo 'Uknown Exception caught. Terminating Script.';
    echo $e->getMessage();
    die;
}
In that example.. the MyClass object will, of course, always throw a DummyException, but in the catch block specific for catching the DummyException we can set a routine for what to do when that type of Exception is thrown. In this case, nothing. The example also shows that you can use multiple catch blocks. All that one does is catch any exception that is not a DummyException, because the script does not know the exact type of exception, or rather, the developer has not created a catch() block to accomodate for it, it prints the message and terminates the script.

A more complex example of try catch from one of the systems I've used in the past. (bit OTT for my liking but meh..):

Code: Select all

/*
* user login..
*/

try {

    /*
    * create User object
    */    
    $user = new User($db);
    /*
    * login
    */    
    $user->login($$METHOD['uname'], $$METHOD['pass']);

/*
* ConnectionException thrown when Database, or connection to Database, not available.
*/
} catch (ConnectionException $e) {

    /* log the exception for investigation */ 
    Error::logException($e);
    /* raise incident for support */
    Error::raiseIncident($e);
    /* generate user friendly message */ 
    Error::setMessage('There has been an error connecting to the Database.' 
    . 'An incident report has been raised. Please try again later.');
    /* terminate */
    Error::terminate();

/*
* AuthenticationException thrown when user credentials are incorrect/invalid.
*/
} catch (AuthenticationException $e) {

    /* generate user friendly message */
    Error::setMessage('Username and/or Password incorrect. Please try again.');
    /* terminate */
    Error::terminate();

/*
* UserProhibitedException thrown when user has been banned.
*/ 
} catch (UserProhibitedException $e) {

    /* generate user frindly exception */
    Error::setMessage('Sorry, that account has been banned. Please contact the administrator for further details.');
    /* terminate */
    Error::terminate();

/*
* Unknown Exception - log and set for highest priority.
*/
} catch (Exception $e) {

    /* log the error for investigation */
    Error::logException($e);
    /* raise incident and set high priority */
    Error::raiseIncident($e);
    Error::setPriority(1);
    /* generate user friendly message */
    Error::setMessage('There has been an error.'
    . 'An incident has been raised and will be investigated. Please try again later.');
    /* terminate */
    Error::terminate();    

}
User avatar
daedalus__
DevNet Resident
Posts: 1925
Joined: Thu Feb 09, 2006 4:52 pm

Post by daedalus__ »

Did you hear that sound?

It sounded like... a large gust of wind above my head!

lol!

I'll understand everything someday but until then..

whoosh!
jamiel
Forum Contributor
Posts: 276
Joined: Wed Feb 22, 2006 5:17 am
Location: London, United Kingdom

Post by jamiel »

Try something, something goes wrong, throw an exception, catch it ... fix it or decide how to go from here to stop a blank screen for the user. Exceptions are an important step forward in error handling and Enterprise development in PHP. Definately worth getting your head around.
User avatar
daedalus__
DevNet Resident
Posts: 1925
Joined: Thu Feb 09, 2006 4:52 pm

Post by daedalus__ »

Thoughts, opinions, questions, comments, praises, flames, and money all appreciated and accepted!

I fixed it up, and used that observer pattern, which I completely understand now, thanks everyone!

Code: Select all

<?php
class ErrorHandler
{
	// Properties
	private $observers = array();	

	// Constructor and Destructor (if needed)
	public function __construct()
	{
		set_error_handler(array($this, 'CallObservers'));
	}

	// Load Observers
	public function LoadObserver($object)
	{
		$this->observers[] =& $object;
	}

	// Unload Observers
	public function UnloadObserver($id)
	{
		unset($this->observers[$id]);
	}

	// Call Observers
	public function CallObservers($level, $message, $file, $line)
	{
		for ($i = 0; $i < $this->CountObservers(); $i++)
		{
			$this->observers[$i]->HandleError($level, $message, $file, $line, $this->CountObservers());
		}
	}

	// Methods
	private function CountObservers()
	{
		return count($this->observers);
	}
}

class ErrorHandlerToBrowser
{
	// Properties
	private $friendly;

	// Constructor
	public function __construct($friendly)
	{
		$this->SetFriendlyErrors($friendly);
	}

	// Setters
	private function SetFriendlyErrors($int)
	{
		if (is_int($int) != FALSE)
		{
			$this->friendly = (($int > 0) != FALSE) ? 1 : 0;
		}
	}

	// Getters
	private function GetFriendlyErrors()
	{
		if (isset($this->friendly))
		{
			return $this->friendly;
		}
	}

	// Methods
	public function HandleError($level, $message, $file, $line, $observers)
	{
		// First check to see if friendly messages are on. 
		if (1 == $this->GetFriendlyErrors())
		{
			print "We're sorry, an error has occured.<br />\n";
		}
		else
		{
			print "<pre>";
			print "<b>$level</b>\n$message\n<i>$file</i> on line <b>$line</b>";
			print "</pre>";
		}
		// Check to see if this is the only observer
		if (1 == $observers)
		{
			print "This Error has <b>not</b> been logged, please report it to an administrator.";
		}
		else
		{
			print "This Error has been logged, you may refresh the page and re-try the operation, or you may go <a href=\"/\">back</a> to the home page.";
		}
	}
}

class ErrorHandlerToDatabase
{
	// Properties
	private $table;

	// Constructor and Destructor
	public function __construct($table)
	{
		$this->SetTableName($table);
	}

	// Setters
	private function SetTableName($table)
	{
		if (is_string($table) != FALSE)
		{
			$this->table == $table;
		}
	}

	// Getters
	private function GetTableName()
	{
		return $this->table;
	}

	// Methods
	public function HandleError($level, $message, $file, $line, $count = '')
	{
		$sql = sprintf("INSERT INTO %s (id, level, message, file, line) 
						VALUES (NULL, '%s', '%s', '%s', '%d')",
						$this->GetTableName(), $level, $message, $file, $line);
		mysql_query($sql);
		if (mysql_affected_rows() > 0)
		{
			print "The error was logged into the database successfully!";
		}
	}
}

class ErrorHandlerToLog
{
	// Properties
	private $log_file_path;
	private $log_file_name;
	private $log_file_frequency;	// This is how often to create a new log file.
									// Daily, Weekly, Monthly, Yearly, Never
	// Constructor
	public function __construct($log_file_path, $log_file_name, $log_file_frequency)
	{
		$this->SetLogFrequency($log_file_frequency);
		$this->SetLogPath($log_file_path);
		$this->SetLogName($log_file_name);
	}

	// Setters
	private function SetLogPath($log_file_path)
	{
		$this->log_file_path = $log_file_path;
		if (substr($log_file_path, strlen($log_file_path)-1, 1) != '/')
		{
			$this->log_file_path .= '/';
		}
	}

	private function SetLogName($log_file_name)
	{
		switch ($this->GetLogFrequency())
		{
			case 'daily':
				$this->log_file_name = $log_file_name.date('\_M\_d\_Y');
			break;
			case 'weekly':
				$this->log_file_name = $log_file_name.date('\_W\_Y');
			break;
			case 'monthly':
				$this->log_file_name = $log_file_name.date('\_M\_Y');
			break;
			case 'yearly':
				$this->log_file_name = $log_file_name.date('\_Y');
			break;
			default:
				$this->log_file_name = $log_file_name;
			break;
		}
		$this->log_file_name .= '.log';
	}

	private function SetLogFrequency($log_file_frequency)
	{
		$valid_args = array('daily', 'weekly', 'monthly', 'yearly', 0);	
		if (in_array($log_file_frequency, $args) != FALSE)
		{
			$this->log_file_frequency = $log_file_frequency;
		}
	}

	// Getters
	private function GetLogPath()
	{
		return $this->log_file_path;
	}

	private function GetLogName()
	{
		return $this->log_file_name;
	}

	private function GetLogFrequency()
	{
		return $this->log_file_frequency;
	}

	// Methods
	public function HandleError($level, $message, $file, $line, $count = '')
	{
		$error_str = $level.', '.$message.', '.$file.', '.$line;
		$file = fopen($this->GetLogPath().$this->GetLogName(), 'a') or die('could not open');
		fwrite($file, $error_str);
		fclose($file);		
	}
}


function __autoload($class_name)
{
	include_once("inc/$class_name.class.php");
}

$ErrorHandler = new ErrorHandler();

$ErrorHandler->LoadObserver(new ErrorHandlerToBrowser(1));

getimagesize('bleat.jpg');
I realise that it lacks error handling of most or any kind but I will fix that later.

I want to try out this try...catch stuff but don't completely understand it yet..

EDIT: I realise there are problems with the ToLogFile class or whatever I called it, but at the end of a 20 hour day, I am too tired to finish it. :(
Last edited by daedalus__ on Thu Jul 27, 2006 2:07 am, edited 1 time in total.
User avatar
daedalus__
DevNet Resident
Posts: 1925
Joined: Thu Feb 09, 2006 4:52 pm

updated, look up

Post by daedalus__ »

updated, look up
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

That class seems rather complicated for an error handling class. I wrote one last night because it's something I've been putting off. I didn't post it here though cause I don't want to hijack your thread.
User avatar
daedalus__
DevNet Resident
Posts: 1925
Joined: Thu Feb 09, 2006 4:52 pm

Post by daedalus__ »

hijack away!
Post Reply