In this article, we will look at how we can manage exceptions in our application, and how we can log these exceptions for debugging later. We can also set certain exceptions and errors to notify us by email, so we can be notified and get to work resolving the issue straight away.

With PHP, you have two different forms of problem messages. They come in the form of Errors, and Exceptions. Errors are a simpler, and more procedural way of dealing with unexpected failures in an application. Exceptions are an object oriented approach, and give us more flexibility in the way we can respond to, and handle the failure in our application. With this in mind, we want to work mainly with exceptions in our application, however PHP utilises errors by default to handle problems with an application’s execution. We will look at how we can set a custom error handler, in order to raise exceptions for the default PHP errors. But first, we will look at the difference between an error and an exception in PHP.

We can create a simple error by trying to create an instance of a class that does not exists. An example is the following line of code.

<?PHP // example only

	$File = new File();
	
?>

If we were to run the code above without defining a File class, we would get an error, as the class does not exist! An example of the error message that would be output is shown below.

Fatal error: Class 'File' not found in example.php on line 3

PHP errors come in a variety of different severity levels, from notice, to warning, and fatal error. The error we received above was a Fatal error, and as a result, our script will die, or stop running. We need to have a way to handle these errors, as it generally means there is something not working correctly in our application. If we do not handle the error in some way, the error message may be displayed to the end user, and can reveal information about our code.

An exception, is generally triggered by our own application code to let us know that we have an issue. For example, we can try to open a file, and if opening the file fails, we can throw an exception which we can then handle appropriately. An example of throwing an exception is shown below.

<?PHP // example only
	
	throw new Exception('Something has gone wrong');

?>

If we run the above code, we will get output similar to the output shown below.

Fatal error: Uncaught exception 'Exception' with message 'Something has gone wrong'

Notice how we got a fatal error, because we had an uncaught, or un-handled exception! This is because, like a ball, when an exception is thrown, there is an expectation that the exception will be caught. We can do this with a try/catch block. The example below shows how we might use the try/catch to ensure an integer is a positive number.

<?PHP  // example only

	try {
	
		if($i >= 0){
			echo 'The number is: '. $i;
		} else{
			throw new Exception('The number was not positive');
		}
	
	} catch(Exception $Exception) {
		echo 'There was a problem: '. $Exception->getMessage();
	}
	
?>

In this example, we use a try/catch block and check if the integer $i is greater than or equal to 0. If this is true, we can perform our calculations, or run our intended code. In this example, we simply output ‘The number is: ‘ followed by the value of our integer.

If the integer is less than 0 however, we can throw an exception, and provide a meaningful message with the exception. In this case, ‘The number was not positive’. Our catch block then catches any Exception thrown and allows us to deal with the exception. In this case, we will output ‘There was a problem: ‘ followed by the message provided when the exception was thrown. If we ran this example code, with $i = -1, we would get the following output.

There was a problem: The number was not positive

In this case, with a negative number, our exception is thrown and we use the catch block to generate the above output. In a real scenario, we would use the catch block to perform more useful actions like logging the error for debugging, or in this case asking the user for a valid positive number.

If we ran the same example with $i = 1, we would get our expected output, shown below.

The number is: 1

In addition to using the PHP Exception class to throw exceptions, we can extend and customise it to suit our particular needs. For example, we may wish to create custom exceptions to handle file errors FileException, or datastore problems DatastoreException. Extending the Exception class works like extending any PHP class, and allows you to build in extra functionality. In our application, we want to be able to log some exceptions, and email some exceptions to the developer for debugging. So we can extend the default Exception class, and add this functionality.

<?PHP // core/buziexception.class.php

class BuziException extends Exception {
	
	protected $Store;
	protected $Log = false;
	protected $Mail = false;
	
	public function __construct($message = null, $code = 0, Exception $previous = null){
		parent::__construct($message, $code, $previous);
		
		if($this->Log){
			$this->Store = Container::newErrorStore();
			$this->logException();			
		}
		if($this->Mail){
			$this->mailException();
		}

		
	}
	
	public function logException(){
		$Error = new Error();
		$Error->Type = get_class($this);
		$Error->Message = $this->message;
		$Error->Code = $this->code;
		$Error->File = $this->file;
		$Error->Line = $this->line;
		$Error->Trace = $this->getTraceAsString();
		
		$this->Store->upsert($Error);
	}
	
	public function mailException(){
		$Subject = get_class($this).' - Exception Caught';
		$Message = '<p>Exception: '.get_class($this).'<p>Code: '.$this->code.'<p>File: '.$this->file.'<p>Line: '.$this->line.'<p>Trace: '.$this->getTraceAsString();
		App::sendMail(App::$SupportEmail, App::$SupportEmail, $Subject, $Message);
	}
	
	public function setStore($Store){
		$this->Store = $Store;
	}
	
	
	
}
	
?>

Above, we have created a class BuziException which extends the Exception class. We have defined three protected variables, $Store, $Log and $Mail. We have also set a default value of false to the Log and Mail variables.

Next, we have defined a __construct() method, that takes in the same values as the standard Exception class. We then call the parent::__construct() method in order to create the exception. Next, we test if the $Log variable is set to true, and if so, we obtain a connection to the datastore, using a new static method in our container newErrorStore() (Shown below). And we then call the logException() method we have defined later in the class. We also test if $Mail has been set to true, and if so call our mailException() method. This allows us to extend the BuziException class, and simply define the $Log and $Mail variables as either true or false to determine if the exception type should be logged, and if it should be mailed. Now any time an exception of that type is thrown, these actions will take place if set to true.

Our logException() method creates an entity of type Error (definition of this class shown below), and sets the Type variable to the current class type (which will be the name of the extended exception class). We then set all of the information available from the exception into our Error entity. Once we have set the information, we can then upsert our entity into the store using the Store’s inbuilt upsert() method.

The mailException() method creates a subject line for the email, and compiles the relevant exception information into a message. The method then calls a new static method (defined below) in our App class to sendMail(). We provide a to and from address (in this case I have defined a static variable in our App class which we can use to define a support email address), our $Subject and our $Message. The App class’s sendMail() method will then handle the delivery of our email exception notification.

<?PHP	// core/error.class.php
	
	class Error extends GDSEntity{

	}

?>


<?PHP	// core/errorstore.class.php
	
class ErrorStore extends GDSStore{
	
	public function buildSchema(){
		$Schema = new GDSSchema('Error');
		$Schema->addString('Type', true);
		$Schema->addString('Message', true);
		$Schema->addString('Code', true);
		$Schema->addString('File', true);
		$Schema->addString('Line', true);
		$Schema->addString('Trace', false);
		return $Schema;
	}
	
	public function __construct($Gateway){
		parent::__construct($this->buildSchema(), $Gateway);
		$this->setEntityClass('Error');
	}
	
}	

?>
	
<?PHP // core/container.class.php -- additions only --
	
	public static function newErrorStore(){
		return new ErrorStore(self::newGateway('Error'));
	}
?>
	
<?PHP // core/app.class.php -- additions only --
	
	public static $SupportEmail = 'info@buziit.com.au';
	
	public static function sendMail($to, $from, $subject, $message){
		$Result = self::sendMailUsingPHPMail($to, $from, $subject, $message);
		if(!$Result){
			$Message = 'Failed sending mail from: '.$from.', to: '.$to.', with subject: '.$subject;
			throw new MailException($Message);
		}
	}
	
	public static function sendMailUsingPHPMail($to, $from, $subject, $message){
		$headers = 'From: '.$from."rn".
		'Reply-To: '.$from."rn";
		
		$Result = mail($to, $subject, $message, $headers);
		return $Result;
	}

?>
	

The Error and ErrorStore definitions follow the pattern we looked at in the Google Datastore – A SaaS Experience article. In the ErrorStore definition, we added the attributes we need to store the exception information.

We then define a newErrorStore() static method in our Container class to assist us with creating a connection to the datastore. When creating the gateway, we specify a namespace of  ‘Error’ to keep our error logging separate from our application data.

In our App class, we define a static sendMail() method which accepts a to and from address, as well as a subject and message. This method then calls another new static method sendMailUsingPHPMail, which utilises PHP’s inbuilt mail method to send our email. The sendMailUsingPHPMail() method then returns true if the email was accepted, and false if it was not accepted by the sending server for delivery. Our sendMail() method then receives the true/false result, and if the result was false, meaning the email was not sent, we throw a MailException to allow us to debug the email failure. The reason the sendMail() method was split to call the sendMailUsingPHPMail() method, is that the sendMail() method gives us a method to call every time we wish to send mail from our application, and we can then customise the service we use to send mail. For example, the inbuilt PHP mail() method that we have used in this example, only allows 100 API calls per day on Google APP Engine, beyond this number, we need to integrate with an external mail provider like SendGrid or Mandrill. When we do this, we can simply update the sendMail() method with the changes, and do not have to find every single place we have sent mail.

When we sent our mail in sendMail(), we tested the result, and threw an exception if the send failed. The exception we threw was a MailException. But this does not yet exist! We can define the MailException by extending our BuziException, and defining $Log as true. We cannot allow the mailException() method to be called, because if the mail send fails, we will be caught in an infinite loop of trying to send notifications of the MailException, so we will set $Mail to false.

<?PHP	// core/mailexception.class.php

class MailException extends BuziException{
	protected $Log = true;
	protected $Mail = false;
}
	
?>

We now have a way to log and email different exception types, and we can create as many useful exceptions types as we need for our application. In order to ensure our exceptions are working as expected, we need a few tests!

<?PHP	// tests/BuziExceptionTest.php

class BuziExceptionWithLog extends BuziException{
	protected $Log = true;
	public $Store;
	public $Gateway;
	
	public function __construct($message = null, $code = 0, Exception $previous = null){
		Exception::__construct($message, $code, $previous);
		
		$this->Gateway = new MockGateway(null, null);
		
		$this->setStore(new MockStore($this->Gateway));
		
		$this->logException();
		
	}
}

class BuziExceptionWithMail extends BuziException{
	public $Mail = true;
}

class BuziExceptionTest extends BuziTest{
	
	public function testCanThrowException(){
		$this->setExpectedException('BuziException', 'Testing exception thrown');
		throw new BuziException('Testing exception thrown');
	}
	
	public function testBuziExceptionLogsException(){
		try{
			throw new BuziExceptionWithLog('This should log an exception');			
		} catch(BuziExceptionWithLog $Exception){
			$Store = $Exception->Gateway;
			
			$this->assertEquals(1, sizeof($Store->Entities), 'Size of store does not match the expected size of 1 entity');
			$Entity = $Store->Entities[0];
			$this->assertEquals('BuziExceptionWithLog', $Entity->Type, 'The exception type does not match the expected BuziExceptionWithLog');
			$this->assertEquals('This should log an exception', $Entity->Message, 'The exception message does not match the one set');
		}
		
	}
	
	public function testBuziExceptionMailsException(){
		$this->setExpectedException('BuziExceptionWithMail', 'Testing exception thrown and mailed');
		throw new BuziExceptionWithMail('Testing exception thrown and mailed');
	}
	
}	
	
?>

We have extended the BuziException class and created BuziExceptionWithLog, and BuziExceptionWithMail classes for testing only. Note, with the BuziExceptionWithLog class, we had to overwrite the existing __construct() method in order to set a MockStore for testing. We then test a standard BuziException, and ensure the expected exception was thrown.

Next, we test our logging with the BuziExceptionWithLog exception. Once the exception is thrown, we then access the MockStore to test that the data stored for the exception is correct.

With the testBuziExceptionMailsException() test, we set the expected exception, BuziExceptionWithMail, and then throw the exception. We cannot catch the email to ensure it matches the exception data, however we know that if the email fails in our App:sendMail() method, it will throw a MailException, which will make this test fail.

Success! Our exceptions are working as we expected. We also want to be able to handle PHP errors in the same way we handle exceptions. This is actually quite easy, as we can use PHP’s set_error_handler() function to handle errors and throw custom exceptions. In our App class, we can define an error handling method, and set the error handler.

To begin, we will define a ExceptionFromError class, which extends our BuziException class. The purpose of this is to provide a custom constructor that will convert our errors into valid exceptions. From there, we can then create a MailException class and a LogException class that both extend the ExceptionFromError class. We can then use either the LogException, or MailException classes as required to handle our errors.

<?PHP // core/exceptionfromerror.class.php

class ExceptionFromError extends BuziException{
	
	public function __construct($message = null, $code = 0, Exception $previous = null, $file, $line, $context){
		$this->file = $file;
		$this->line = $line;
		$this->context = $context;

		parent::__construct($message, $code, $previous);
	}
	
}
	
?>
	
<?PHP	// core/mailerror.class.php

class MailError extends ExceptionFromError{
	protected $Log = true;
	protected $Mail = true;
}
	
?>
	
<?PHP	// core/logerror.class.php

class LogError extends ExceptionFromError{
	protected $Log = true;
}
	
?>

Now that we have a LogError and a MailError exception class, we can define our error handler to throw these exceptions based on the error type. In our App class, we will define a customErrorHandler() method.

<?PHP	// core/app.class.php -- additions only --
public function customErrorHandler($ErrNo, $ErrStr, $ErrFile, $ErrLine, $ErrContext){
	switch($ErrNo){
		case E_ERROR:
			throw new MailError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_WARNING:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_PARSE:
			throw new MailError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_NOTICE:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_CORE_ERROR:
			throw new MailError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_CORE_WARNING:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_COMPILE_ERROR:
			throw new MailError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_COMPILE_WARNING:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_USER_ERROR:
			throw new MailError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_USER_WARNING:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_USER_NOTICE:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_STRICT:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_RECOVERABLE_ERROR:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_DEPRECATED:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
		case E_USER_DEPRECATED:
			throw new LogError($ErrStr, $ErrNo, null, $ErrFile, $ErrLine, $ErrContext);
			break;
	}
}

?>

In the customErrorHandler() method, we have used a switch to determine the error level, and then for each error level, have thrown either a MailError or a LogError exception, based on the errors we wish to log and mail. We can now set our customErrorHandler() method as the error handler for our entire application.

<?PHP	// core/app.class.php -- additions only --
	public function __construct(){
	
		// -- additions only --
		
		//Set error handler
		set_error_handler(array(&$this, 'customErrorHandler'), E_ALL);
	
	}
?>

Here we have used the set_error_handler() function to set our customErrorHandler() method as the error handler. We also need to set an exception handler in order to catch any uncaught exceptions. This is crucial, as we won’t know when we need to catch the errors we have just converted to exceptions. In our exception handler, we will simply output a generic error message to the user, as our custom exception classes already handle logging and notification of the exceptions.

<?PHP	// core/app.class.php -- additions only --

	public function __contruct(){
	
		// -- additions only --
		
		//Set exception handler
		set_exception_handler(array(&$this, 'customExceptionHandler'));

		//Set error handler
		set_error_handler(array(&$this, 'customErrorHandler'), E_ALL);
	}
	
	public function customExceptionHandler($Exception){
		echo '<p>Something has gone wrong, we apologise for the inconvenience. The developer has been notified of the error, and is working to resolve the issue.';
	}

?>

We have now set custom exception and error handling for our application! We have also tested the mailing and logging of our custom exceptions. In order to test our custom error handler with PHPUnit, we would need to modify our classes to allow us to replace the datastore connection with a MockStore. This would be impractical to do to test such a simple change to our application. So we will test that our application throws the correct exceptions manually.

In our views/dashboardview.class.php file, we can use PHP’s trigger_error() function to trigger an error that uses our LogError, and one that uses our MailError exception.To see the correct output, I have added a single line to our ExceptionFromError class’s constructor (shown below).

We can then trigger errors from our dashboardview.class.php file and view the output in the browser.

<?PHP // core/exceptionfromerror.class.php -- additions only --

	// -- addition to __construct method --
	echo '<p>Exception Type: '.get_class($this);
	
?>
	
<?PHP // views/dashboardview.class.php -- additions only -- // Test 1

	public function render(){
		echo 'Hello World, from Dashboard';
		trigger_error('A Log Error', E_USER_WARNING);
	
		return true;
	}
	
?>
	
<?PHP // views/dashboardview.class.php -- additions only -- // Test 2

	public function render(){
		echo 'Hello World, from Dashboard';
		trigger_error('A Mail Error', E_USER_ERROR);
	
		return true;
	}
	
?>
	

The output of the two tests in the browser are shown below. Notice how when the error is triggered, our exception handler is also triggered and outputs our standard error message. This shows that both our error handler, and our exception handlers are working correctly.

Our exception and error handlers are now functioning as expected! We can now comment out, or remove the testing code we added in the last code block. This was a lengthy article! But worth the trouble, as we can now automatically log and email errors and exceptions as appropriate, and we will know whenever a user runs into trouble. This will allow us to quickly track bugs and remove them from the application.

In the next article, we will look at adding Users to our application, so that we can then look at implementing authentication and security measures in our application.