Although solving all the bugs and potential errors in your code sounds like a nice idea, it's not really possible. The main reason for this is because it's hard to predict how your code will operate in all scenarios, so you can't write code to handle it all.
The solution here is to write exception handlers, which allow you to explicitly state what PHP should do if there's a problem in a block of code. Exceptions are interesting because they all come from the root class Exception, but you can extend that with your own custom exceptions to trap specific errors.
The basic exception handler uses try/catch blocks to encase blocks of code in a virtual safety barrier that you can break out of by throwing exceptions. Here's a full try/catch statement to give you an idea of how it works:
<?php
try {
$num = 10;
if ($num < 20) {
throw new Exception("D'oh!");
}
$foo = "bar";
} catch(Exception $exception) {
print "Except!\n";
}
?>
In that example, PHP enters the "try" block and starts executing code. When it hits the line "throw new Exception", it will stop executing the "try" block, and jump to the "catch" block. Here it checks each exception option against the list in "catch" and executes the appropriate code. Once PHP has left the "try" block, it will not return to it, which means that the line $foo = "bar" will never be executed.
The way the catch block works might seem a little confusing first, but I'll show you an example in a moment where we catch multiple exceptions and rely on PHP to jump to the appropriate one. First, though, I want to explain why the code is catch(Exception $exception).
The Exception class in there is necessary because PHP decides which catch block to execute by looking for the same class type as was thrown. Well, that's the "easy" way of looking at it: what actually happens is that PHP searches each catch block, using what is essentially an "instanceof" check on it. This means that if the exception thrown is of the same class as the exception in the class block or if it is a descendant of that class, PHP will execute that catch block. Yes, that sounds complicated, and yes, it sounds more complicated than it actually is.
The $exception variable after the Exception class is there because PHP actually hands you an instance of that Exception class, set up with information about the exception you've just experienced. As all exceptions extend from the base class Exception, you get a basic level of functionality no matter what you do. What's more, most of the functions in the Exception class are marked "final", meaning they can't be overridden in inherited classes, again guaranteeing a set level of functionality. For example, you can call $exception->getMessage() to see why the exception was thrown (the "D'oh!" part in the throw() statement), you can call getFile() to see where the exception was called, etc.
Here's an example that demonstrates how PHP handles multiple catch blocks:
<?php
class ExceptFoo extends Exception { }
class ExceptBar extends ExceptFoo { }
try {
$foo = "bar";
throw new ExceptFoo("Baaaaad PHP!");
$bar = "baz";
} catch (ExceptFoo $exception) {
echo "Caught ExceptFoo\n";
echo "Message: {$exception->getMessage()}\n";
} catch (ExceptBar $exception) {
echo "Caught ExceptBar\n";
echo "Message: {$exception->getMessage()}\n";
} catch (Exception $exception) {
echo "Caught Exception\n";
echo "Message: {$exception->getMessage()}\n";
}
?>
That will output the following:
Caught ExceptFoo
Message: Baaaaad PHP!
Of course, that makes sense: we throw an ExceptionFoo, so PHP jumps to the ExceptionFoo catch block. Now try this code:
<?php
class ExceptFoo extends Exception { }
class ExceptBar extends ExceptFoo { }
try {
$foo = "bar";
throw new ExceptBar("Baaaaad PHP!");
$bar = "baz";
} catch (ExceptFoo $exception) {
echo "Caught ExceptFoo\n";
echo "Message: {$exception->getMessage()}\n";
} catch (ExceptBar $exception) {
echo "Caught ExceptBar\n";
echo "Message: {$exception->getMessage()}\n";
} catch (Exception $exception) {
echo "Caught Exception\n";
echo "Message: {$exception->getMessage()}\n";
}
?>
This is where the confusion sets in: it outputs the very same thing this time. Why? Because, as I said, PHP matches the first catch block handling the exception's class or any parent class of it. Because ExceptionBar inherits from ExceptionFoo, and the ExceptionFoo catch block comes before the ExceptionBar catch block, the ExceptionFoo catch block gets called first.
You can rewrite the code to this:
<?php
class ExceptFoo extends Exception { }
class ExceptBar extends ExceptFoo { }
try {
$foo = "bar";
throw new ExceptBar("Baaaaad PHP!");
$bar = "baz";
} catch (ExceptBar $exception) {
echo "Caught ExceptBar\n";
echo "Message: {$exception->getMessage()}\n";
} catch (ExceptFoo $exception) {
echo "Caught ExceptFoo\n";
echo "Message: {$exception->getMessage()}\n";
} catch (Exception $exception) {
echo "Caught Exception\n";
echo "Message: {$exception->getMessage()}\n";
}
?>
This time we have the exception classes ordered descending by their inheritance, so the script works as we would expect.
Here's a slightly more complicated example that should help clarify how other Exception the can be used:
<?php
class OutOfBoundsException extends Exception {
// nothing special here
}
class NotFooException extends Exception {
// nothing here either
}
try {
$num = 310;
if ($num < 20) {
throw new OutOfBoundsException("Out of bounds");
}
$foo = "bar";
if ($foo != "foo") {
throw new NotFooException("This variable is not set to 'foo'!");
}
} catch(OutOfBoundsException $exception) {
var_dump($exception);
print "Out of bounds exception: $exception->file:$exception->line - $exception->message\n";
} catch (NotFooException $exception) {
print "Not foo exception: $exception->file:$exception->line - $exception->message\n";
} catch (Exception $exception) {
print "Some other exception: $exception->file:$exception->line - $exception->message\n";
}
?>
There should be no surprises in there: there are two exception classes again (except not inherited this time), plus multiple catch blocks.
Note that if you want to you can throw an exception inside a catch block - either a new exception or just the old exception again. This is called rethrowing the exception, and is commonly used if you have ascertained that you cannot (or do not want to) handle the exception there.
Using this form of debugging allows you to have debugging code next to the code you think has a chance of breaking, which is much easier to understand than having one global error-handling function. Whenever you have code that you know might break and want to include code to handle the problem in a smooth manner, try/catch is the easiest and cleanest way of debugging.
If you're using PHP 5.5 or later, you can take advantage of a new keyword called finally, which lets you ensure some code will always be executed. You can use this alongside try/catch if you want, making try/catch/finally, or you can just use try/finally. Either way, the code you place inside your finally block will always be run, so it's a smart place to put any critical clean up code.
If an exception is thrown and matches an existing catch block, that catch code will be executed before the finally block is run. If there is no matching catch block, or if no exception is thrown, the finally code will be run regardless. In short, it's perfect for ensuring some code is always run.
It's obviously down to you to determine what counts as "critical clean up code", but a good start is freeing up any system resources that you know PHP won't collect for you. Similarly, writing out any files you were preparing, clearing buffers, and so on – these are all good candidates.
Using try/catch/finally or just try/finally is easy enough, but here's an example to get you started:
<?php
try {
complicatedCode();
} catch (NastyException $e) {
handleError($e);
} finally {
importantCleanup();
}
?>
Want to learn PHP 7?
Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!
If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!
Next chapter: Backtracing your code >>
Previous chapter: Handling MySQL errors
Jump to:
Home: Table of Contents
Copyright ©2015 Paul Hudson. Follow me: @twostraws.