How to use try…catch PHP Exceptions to make your Code more Stable

Creating an MVC Framework from scratch has helped me realize just how important it is to have mechanisms in place that will help you deal with errors or unexpected output. If your script accepts input from a user or a third party script, rest assured your script will have to deal with bad data, no matter how unlikely that is.

No matter what you’re coding, it’s always a good idea to be able to check for data that can potentially crash your script or stop a process from completing. In the past, you might have done this using if statements:

$iDivider = $_GET['divider'];

// If divider is 0, don't continue.

if ($iDivider == 0) {
    echo "Sorry, number cannot be 0.";
} else {
    echo "Total: " . (5/$iDivider);
}

The exact functionality above can be replicated using the try…catch exception:

$iDivider = $_GET['divider'];

// If divider is 0, don't continue.

try { 

    if ($iDivider == 0) {
        throw new Exception("Sorry, number cannot be 0.");
    }
    echo "Total: " . (5/$iDivider);

} catch (Exception $e) {

    echo $e->getMessage();

}

The two snippets above do the exact same thing.

How do PHP Exceptions work?

Exceptions use a try…throw…catch mechanism. This is actually quite easy to understand; you try to do something, if a problem is detected, you throw an error and catch it at the end of your code.

The moment you throw an error, it is immediately caught so any remaining code following the throw statement is not executed.

When an error is caught by the catch block, we immediately know quite a bit of information. For instance, we know on what line and in what file the error happened:

try { 

    if (...) {
        throw new Exception("Sorry, there's an error.");
    }

} catch (Exception $e) {

    echo "Message: " . $e->getMessage() . "<br />";
    echo "File: " . $e->getFile() . "<br />";
    echo "Line: " . $e->getLine();

}

That would output:

Message: Sorry, there's an error.
File: /path/to/file
Line: 4

Extending Exceptions

The great thing about having object oriented exceptions is the way you can extend them and add your own functionality. For instance, say we always wanted to show the message, file and line in our error report, we could create a new exception class:

class MyException extends Exception
{
    public function getErrorReport() {
        echo "Message: " . $this->getMessage() . "<br />";
        echo "File: " . $this->getFile() . "<br />";
        echo "Line: " . $this->getLine();
    }
}

try { 

    if (...) {
        throw new MyException("Sorry, there's an error.");
    }

} catch (MyException $e) {

    $e->getErrorReport();

}

See what’s happening here? By creating a class which extends the Exception class, we retain all its functionality while adding our own. In this case, we are able to format the way we display errors throughout the entire site by simply updating our MyException class, rather than modifying each catch block.

Multiple Catch Blocks

It is possible to group your exceptions so you can handle them differently. For instance, you might want to send emails to yourself when a specific error occurs. Here’s an example:

class DBException extends Exception {
    public function getError() {
         // send an email
         sendemail(...);
         return $this->getMessage();
    }
}
class FileException extends Exception {
    public function getError() {
         // update a log file
         updatelog();
         return $this->getMessage();
    }
}

try { 

    if (!$bDBConnection) {
        throw new DBException("Problem with Database");
    }
    if (!$bFile) {
        throw new FileException("Problem with File");
    }

} catch (DBException $e) {

    echo $e->getError();

} catch (FileException $e) {

    echo $e->getError();

}

Above, we’re creating a custom Exception for database errors that emails us each time an error occurs. By grouping our Exceptions, we can quickly know which part of our script has failed and decide how to handle it.

Nested Exceptions

As of PHP 5.3, it is now also possible to nest exceptions. This means that your catch block can actually call another Exception. I will use the examples above to create exceptions which will handle two different types of errors and in the end, show me the error message, page and line of each errors.

// This class handles DB errors and sends an email
// each time an error occurs

class DBException extends Exception {
    public function processError() {
         // send an email
         sendemail(...);
    }
}

// This class handles File errors and logs
// each error that occurs

class FileException extends Exception {
    public function processError() {
         // update a log file
         updatelog();
    }
}

// This class handles the output and displays all
// messages, files and lines of each error

class OutputException extends Exception
{
    public function getErrorReport() {
        echo "Message: " . $this->getMessage() . "<br />";
        echo "File: " . $this->getFile() . "<br />";
        echo "Line: " . $this->getLine();
    }
}

// If you are nesting Exceptions, you need to have multiple
// try blocks nested inside each other. The outermost try
// block should be followed by the catch block that actually
// displays the error (and doesn't call another Exception)

try {

    // The inner try block will throw specific Exceptions
    // based on the error encountered. The catch blocks
    // will then process the DBException or FileException
    // and throw the OutputException to display the error

    try { 

        if (!$bDBConnection) {
            throw new DBException("Problem with Database");
        }
        if (!$bFile) {
            throw new FileException("Problem with File");
        }

    } catch (DBException $e) {

        $e->processError();
        throw new OutputException($e->getMessage());

    } catch (FileException $e) {

        $e->processError();
        throw new OutputException($e->getMessage());

    }
}

catch (OutputException $e) {

	$e->getErrorReport();

}

The benefit of this is the way we can create a hierarchy of Exceptions allowing us to deal with individual problems while still having all custom Exceptions available to use.

What’s next?

The above examples hopefully allowed you to understand exactly how the try…catch exception works. As always, the best and probably only way to really learn the techniques mentioned here are to try it yourself. You will learn much more by creating scripts that don’t work and figuring out what’s wrong.

The real benefit of Exceptions lies in the way you can throw them from inside functions or objects, allowing you to find the location of errors easily. For instance, this code is a good example:

class DBException extends Exception { }

function connectDB() {

    // Connect to DB (function name is an example)

    $bConnection = getDBConnection();
    if (!$bConnection) throw new DBException("Problem!");

}

try { 

        // call the connectDB() function
	connectDB();

} catch (DBException $e) {

    echo $e->getMessage();

}

You will see that the Exception is being thrown from inside the function itself rather than directly between the try block. You can do the same with objects you call allowing you to keep a centralized error catcher.

I have only covered the basics here but hopefully it will be enough to inspire you to start playing around with Exceptions and discover the real benefits of using them. Happy coding!

Do you have anything to add?

Don't forget to check out more posts from the Tutorial of the Week section.

blog comments powered by Disqus
.