Wednesday 13 March 2013

PHP Exceptions: Throwing and why

I've found a lot of new developers have a tough time getting their head around exceptions.
The normal flow of execution looks like the below:


let's take that you need to retrieve something from a database. Here you can have two common problems.
  1. A problem reading from the SQL data store
  2. The input your checking against is bad/not valid
Both of these are serious issues that need to be addressed!



This will lead to a world of pain, not only for the running script but for consistency on that data set going forward if this is not addressed.

I believe not throwing & handling exceptions comes from a bad precedence that when a function fails It will just return a boolean false. I can understand this as a legacy from the older versions of php, but now seriously how many dev actually check to see if the returned value is a false.
Instead they just continue executing, assuming that because the program hasn't halted everything is fine.

The simplest way you should think of exception handling is like nested dolls. As you call functions you are getting more specific into the operations of your application and this means the type of exceptions that will happen are specific to what is happening within the function. This is why we create our own sub-types of exceptions.

A lot of times I see code like this:

function read(){

  try{
    $con=mysqli_connGct("localhost","user","pass","db");

    $result = mysqli_query($con,"SELECT * FROM tableA");

    mysqli_close($con);

    $aResult = array();

    while($row = $result->fetch_object()){ 
            $aResult[] = $row; 
    } 

    return $aResult;
  }
  catch(Exception $_exception){

  }
}

With the above example you are just catching everything that could go wrong. this makes it very hard to handle different types of problems as they occur.

What we need to do is an form the calling function that something has gone wrong. The best way of doing this is by creating a custom exception that best matches our situation.


class StorageException extends Exception
{ }

That's it. Simple!
With a "StorageException" we can catch it in the calling function (or higher in the call stack) that tells of something went wrong. You may notice that I did not use the word SQL, in the name of the exception. This allows me to change the underlying storage system e.g. moving from a SQL server to say MongoDB without without changing the exception name.


catch(Exception $_exception){

  throw new StorageException("Problem reading from tableA",0,$_exception);

}

Here I'm 1)Describing the problem 2)passing an error code and 3) passing the original exception

Next we need to replace Exception with something more specific like mysqli_sql_exception.
Now if there is a more specific type of exception it will need to be caught at higher level. This is important as it means that only specific sql exceptions are caught at this layer.




catch(mysqli_sql_exception $_exception){

  throw new StorageException("Problem reading from tableA",0,$_exception);

}

Now we encapsulate the entry point of the script in a try/catch block so that any exceptions not caught specifically will be finally handled. This should be the only place in your code that catches the base type of exception

try{
   $values = read();
   foreach (values as $value){
      echo $value;
   }
}
catch(Exception $_exception){

  // Log exception info

}

Lastly, here's a list of the functions that you can call on your exception in order to drill down into what happened.
  • getMessage()
    •  gets the exception’s message
  • getCode()
    • returns a numerical code that represents the exception
  • getFile()
    • returns the file where the exception was thrown
  • getLine()
    • returns the line number in the file where the exception was thrown
  • getTrace()
    • returns the an array of the backtrace() before the exception was thrown
  • getPrevious()
    • returns the exception thrown before the current one, if any
  • getTraceAsString()
    • returns the backtrace() of an exception as a string instead of an array
  • __toString()
    • returns the whole exception as a string. Is overrideable.
... continue reading!