PHP5: Exception or not to Exception
After a late night yesterday evening, I came with the idea of looking briefly into a good method of passing error messages back from new created class to the caller. This might sound somewhat of a back dated idea to explore but it allowed me to understand and appreciate the benefit of exceptions.
This idea was raised due to me thinking about how would it be best to pass errors back to a caller from an object without exceptions, the most recognisable path I discovered was using reference parameters - I hope you find this little look into exceptions helpful.
Overview of Non-Exception based error management
————————————————————————
I will briefly run down a system of error checking without using exceptions, I will highlight the slight difficulties you can run into and should be aware of if using such a method.
Code not using exceptions for managing errors:
class myNewClass {
function __construct( & $errorObjectRef )
{
/* Supply a varaible to allow for transfer of error object */
$errorObjectRef = ($this->init( $error ) == false) ? : $error : null;
}
function init ( & $errorObjectRef )
{
//... blah ...
if (...)
{
$errorObjectRef = new errorObject("A message", 1002);
return false;
}
else if (...)
{
$errorObjectRef = new errorObject("A message", 1004);
return false;
}
return true;
}
}
/**
* call the class
*/
$obj = new myNewClass($errorObj);
if (! is_null($errorObj) ) /* an error has occured */
{
/* error occured, object should pass back a error object */
echo $errorObj->message();
}
So lets start with the class itself, you will notice that the constructor has a parameter which is referencable, this allows for me to pass a empty variable which will return an error object back to the caller, the variable passed originally does not need to be initiated as it will receive a variable reference from the class if an error occurs.
The first complication that was clear to me is managing the trace from the original fired error, there is no simple way of see a trace or to manage its flow easily.
If we look at:
$errorObjectRef = ($this->init( $error ) == false) ? : $error : null;
You can see that we call the init() function and and wait for a false value to be returned, we need to pass an empty variable which will be supplied to the constructors parameter which the caller of the class eventually receives. Seems a little like a tongue jerker saying all that, remember this is only a very simple example as well.
Please take the nested if…else… statements in the init() function as a very messy way of managing the error, you could how ever pass $error to each of the associated functions and wait till an error is referenced, but you would then have to create $error parameters for next to near every function in the application to get a true representation of where there error occur-ed, also keep in mind of the danger of over writing the $error variable along the process and losing clarity.
Process Flow
——————
Creating a form of interruption between functions called in the constructor becomes over the top as you will need a large array of nested if…else… statements to catch separate errors and pass it back to the caller.
Here is an example of how it can look quite messy, imagine if you had to manage 20 errors or so:
function __construct( & $error ObjectRef )
{
if ($this->init($error))
{
if ($this->anotherMethod($param, $error))
{
//... blah ...
}
else
{
$errorObjectRef = $error;
}
}
else
{
$errorObjectRef = $error;
}
}
Visibility
—————
The caller has no visibility of where the error first occur-ed unless it is deliberately engineered into the creation of the error object, here is an example of how init() could be slightly changed to compensate for this:
$errorObjectRef = new errorObject(“Error Fired on Line: “.__LINE__.” (“.__FILE__.”) oh bugger! process failed”, 1004);
This method is little long-winded and not very optimal, but its the only way of gaining real clearance of where the error was fired from and allocating a reason, this would become quote obtrusive to the init() function as when ever you wish to add extra functionality to the object, you also need to add more conditional statements to fire correct errors, this is failing the concept for object orientation.
Caller Logic
——————
The caller has more work to do as they need to manage the variable that is being returned from the class, this means adding yet more if…else… logic to handle the situation before continuing with the application.
Now, to the exceptions…
==========================
Overview of Exceptions
———————————
If you have not had any experience of exceptions in the past, you will be mildy surprised on how easy its to manage the capturing of errors. Have a brief read of the code below and you will be able to see it is already allot cleaner then the non-exception based error management.
class errorMethod {
function __construct( )
{
try {
$this->init();
/* can now easily perform more functions with no worry it will process beyound the errorious function */
} catch (Exception $e) {
throw $e;
}
}
function init()
{
//... blah ...
if (...) {
throw new Exception("A message", 1002);
} else if (...) {
throw new Exception("A message", 1004);
}
}
}
/**
* call the class
*/
try {
$obj = new errorMethod();
}
catch ($e Exception) {
echo $e->message();
}
Already you can see a large reduction in the if…else… statement, the only place it does occur will be the init() function which is display as such for simplicity, infact you can completely remove the conditional statements and move the “throw new Exception();” into the process the if(…) statement is calling.
Process Flow
——————
Now the beauty of exceptions is the fact it kills all process flow soon as an exception is fired, if an error occurs by throwing a new exception the program will automatically go to its root “catch (Exception $e) { … }” segment of code for the programmer to manage, you have options of:
o Throwing the exception down the program to allow a parenting catch() statement to manage the request.
o Display the error and crash the program so no more damage can be done.
o handle the error and fix the problem and re-execute the erroneous process.
As I’ve high lighted before, the code looks cleaner, this is proven to me by comparing how we can extend the construct() function using exception compared to the previously explained system. Firstly there is no requirement of if…else… statements, we simply place all our code into the try {} segment and allow catch() wait for an error in our business logic which should throw an exception.
function __construct( )
{
try
{
$this->init();
$this->anotherMethod($param);
}
catch (Exception $e)
{
throw $e;
}
}
Notice the “throw $e;” statement, this allows you to defer the error to the caller of the construct() function, so anyone who calls the class will receive the exception and can then write there own logic on how to manage the classes errors.
A word of warning with Exceptions, they can VERY easily be used and abused which make error management confusing at times. You need to plan how exceptions run throw your process and where to catch errors and then what to do with them. A good example of this is trying to manage PDO errors and returning the status of the database communication without the user of the application seeing to much, merely receiving an minimal error.
Visibility
—————
Exceptions and visibility is like a god send, as we can receive the exact line, file, function and process trace on how the error occurred. Where ever you “throw new Exception()” is where the line is set, if you are smart and use exceptions in each function/process which could have an error, you should never really need to use if statements to manage the exceptions path back to the class caller.
Exceptions give a foreign user of the class/library a clear and consistant error management path which is strictly defined by the
try{} catch() code.
Caller Logic
——————
A great feature of exceptions being classes them self’s is you can extend them to contain your own logic for managing where exception message/information is dumped to. In most cases its to screen, but in some instances you may wish to dump a nice “Sorry” message to screen and then a trace dump or so can be saved/mailed to the programmer.
When calling the class you wish to gain an instance from, you should receive this with no hassle or error check because all the logic is deferred to the catch statement which leaves the programmer to consentrate on the rest of the program “AND” if an error occurs its always managed and is dumped to the programmer from the root try{} statement.
Evaluation
==========
All in all, I’m very much a exception kind of man as managing the whole process is allot more simple and strictly structured on how the flow works where as with the non-exception code, it allows for great messiness and its very easy to create inconsistencies between caller and class. You need to know a lot more information to manage errors without exceptions.