In this article, we’ll see PHP Exceptions.
Table of Contents
In the world of PHP programming, errors and exceptions are inevitable. When something goes wrong in your code, it’s essential to handle those errors gracefully and provide meaningful feedback to users.
PHP exceptions are a powerful mechanism that allows you to detect, handle, and recover from unexpected situations in your code.
We’ll delve into the fascinating world of PHP exceptions, exploring their purpose, syntax, and best practices for effective error handling.
What is PHP Exception:
PHP Exceptions give us much better handling of errors and allow us to customize the behavior of our scripts when an error (Exception) is encountered.
Previously PHP dealt with errors by having error flags from functions and the trigger_error() function. These were well and good for pre-php5 days but in the new OO environment, we need greater control.
The PHP Exceptions class can be a very useful and significant tool to aid in the reporting and handling of errors.
PHP 5 has an exception model similar to that of other programming languages (like Java). An exception can be thrown and caught within PHP.
Code can be surrounded in a try block, to facilitate the catching of potential exceptions. Each try must have at least one corresponding catch block.
Multiple catch blocks can be used to catch different classes of exceptions. Normal execution (when no exception is thrown within the try block, or when a catch matching the thrown exception’s class is not present) will continue after that last catch block is defined in sequence.
Exception Class hierarchy in PHP
– Exception
– ErrorException
– LogicException
– BadFunctionCallException
– BadMethodCallException
– DomainException
– InvalidArgumentException
– LengthException
– OutOfRangeException
– RuntimeException
– OutOfBoundsException
– OverflowException
– RangeException
– UnderflowException
– UnexpectedValueException
At the root of the class hierarchy, we have the Exception class. All exceptions have to inherit from it (or a subclass), otherwise, you can’t throw it.
Down the hierarchy, we have the ErrorException, the LogicException, and the RuntimeException. The ErrorException
is used to transform errors into exceptions (we’ll see later how).
The other ones are the top two PHP exception objects in the SPL hierarchy. All other built-in exceptions inherit from one of them. I won’t describe them here, since their names are pretty expressive.
Just make sure to throw one of them if they fit your needs. Don’t try to invent similar ones when they are already defined. This helps you to keep your exception architecture clean and focused.
PHP Exception Syntax:
We’ve already seen the basic syntax of throwing php exceptions, but here is the formal method definition:
-
throw new Exception($message, $code, $previous);
All arguments are optional, but you should at least provide a descriptive message. The code
argument is an arbitrary error code that you can provide.
For example, you can define your own codes or use them with HTTP status codes. If you are rethrowing an exception, you can also provide the instance through the previous
argument. This way, both exceptions get raised to the calling method.
After throwing it, you need to catch it somewhere. PHP provides the familiar try/catch construct, but without a finally
block like in Java.
try { $object = new IThrowExceptions(); } catch(Exception $e) { echo "Caught " . $e->getMessage(); }
The code inside the try
block is evaluated, and if an exception is thrown the catch
block tries to handle it. If the exception can’t be handled, it bubbles up the stack.
If the exception doesn’t get handled in your code, PHP terminates. If you expect different exceptions to be thrown, you can catch more than one at the same time:
try { $foo = new Foo(); $foo->bar(); } catch(InvalidArgumentException $e) { echo "Just InvalidArgumentExceptions"; } catch(RuntimeException $e) { echo "Just RuntimeExceptions"; } catch(Exception $e) { echo "I catch everything"; }
The last catch(Exception $e)
is used when no previous catch succeeded. Make sure to go from special to general, because when you move the Exception
block to the top, the exception would never sink down to the RuntimeException
.
Imagine we get a lot of custom exceptions that all have the same meaning. We want to normalize them, so that only one specific type bubbles up the stack. In our example, no matter what kind of exception we receive during _connect
, we want to throw a NetworkException
.
The last thing that comes in very handy are error handlers. Recall the ErrorException
from the beginning? Here’s what you can do with it (taken from here):
function exception_error_handler($errno, $errstr, $errfile, $errline ) { throw new ErrorException($errstr, 0, $errno, $errfile, $errline); } set_error_handler("exception_error_handler"); /* Trigger exception */ strpos();
PHP throws lots of errors and warnings from built-in methods like strpos
. The problem is that you can’t do anything with them in your code until you translate them into exceptions.
That’s why the ErrorException
was implemented. Notice how different the constructor is to the Exception
class, because it mirrors all variables that are passed to the error handler.
This allows you to capture and handle everything that happens during code execution in a uniform way, being it exceptions or errors.
Testing PHP Exceptions
Testing PHP exceptions may seem daunting at first since you need to verify that your code fails.
This usually means that you need to write unit tests with try/catch blocks and then do some kind of assertion inside the catch-block like this (just assume we have some arbitrary assert
functions defined):
<?php class Foo { public function bar() { throw new RuntimeException("My Message", 123); } } $foo = new Foo(); try { $foo->bar(); } catch(RuntimeException $e) { assertEqual($e->getMessage(), "My Message"); assertEqual($e->getCode(), 123); } ?>
PHP 5.3 provides us with a very nice addition to the language: closures. We can write a handy method that lets us test this kind of code very elegantly. Here’s a simple helper method:
function assertException($expected, $closure) { try { $closure(); echo "Exception expected, but not thrown"; } catch (Exception $e) { if($e->getMessage() == $expected) { echo "Correctly thrown"; } else { echo "Exception thrown, but with a different message."; } } }
We run the closure inside the try/catch block and check if the correct message is raised. Now we can test a code snippet like this:
<?php class Foo { public function bar() { throw new RuntimeException("My Message", 123); } } // prints "Correctly Thrown" assertException("My Message", function() { $foo = new Foo(); $foo->bar(); }); ?>
Of course, in a real test suite, you want to replace the echo
statements with debug output and add more intelligence.
You can see that the exception assertion is integrated tightly after the normal tests and no try/catch blocks are used.
Wrapping Up
While PHP exceptions are part of nearly every language, using them correctly and not throwing something just for the sake of it is not easy and requires some thought.
Make sure to think of your PHP exceptions while you design the API, and test and document them correctly. A lot of frameworks do a very good job on this, so keep your eyes open and prepare yourself to learn something new.