Software systems inevitably encounter situations that can cause failures and malfunctions which affect user experience, data integrity, and security. In this blog post, we will learn how to avoid these situations though Exception handling. We will focus on software exceptions and how to address them using Hypertext Preprocessor, or PHP. As you read, you will learn about:
- What exception handling is
- How to implement exception handling (try, throw, catch)
- Handling multiple exceptions
- Creating your own custom exception
- Global exception handler
- Third party frameworks
Let’s get started!
What Is Exception Handling?
Exception handling is the process of making accommodations for errors and responding to them programmatically. Exception handling results in execution of an alternate, but already planned, sequence of code. It can be found everywhere, including software (applications, OS), hardware (electric bulbs, surge protectors), and electrical systems, like your computer’s motherboard.
As far as the software side is concerned, problems can arise from invalid form inputs, erroneous programming logic, network errors, software compatibility issues, unauthorized access, lack of memory, among other factors.
Exception handling, resolves discrepancies via error messages that can help developers debug, and users better understand the requirements of an application. Good Exception practices save users from a miserable user experience and help developers figure out what exactly has gone wrong with the application.
PHP, like all languages, has some predefined exceptions that are raised by the language itself. Some of these are:
- ArgumentCountError
- ArithmeticError
- DivisionByZeroError
- CompileError
- ParseError
- TypeError
Alternatively, exception handling mechanisms can also be used to create problem-specific constraints. If not implemented, the constraints trigger exceptions which can be dealt with through programming.
Think of this example: You want to create a PHP website used to keep the donor record for a local blood donation center. Only people between the ages of 18 and 65 are eligible to donate blood. Any entry with an ineligible age input should trigger an error condition, disqualifying the entry and alerting the user. Furthermore, since the website expects a number input for the ‘age’ field, any non-numeric character or string should also result in an error. Such conditions, if not handled, can either compromise the integrity of the data or can raise run-time errors, affecting the functionality of the system and maybe even crashing it.
How to Implement Exception Handling
The concepts of ‘try’, ‘throw’ and ‘catch’ are prevalent exception handling mechanisms:
Trying some code, looking for any errors thrown, and catching them.
Try: The try block is used to contain the code that is likely to raise an error condition. Any erroneous condition can be programmatically dealt with if and only if it is encountered in the code inside a try block.
<?php
try {
// code likely to give error comes in this block
}
?>
Throw: Exceptions can be raised either by the programming language as a result of syntactical errors and warnings, or they can be explicitly raised using code when a condition is (or isn’t) met. In programming terminology, the raising of errors is known as throwing. Errors can be manually thrown only from inside the try block as shown below:
<?php
try {
throw new Exception("I am the error message!");
}
?>
The throw keyword is used to raise an instance of the exception class with a string that provides information about the exception thrown. Any code in the try block, below the throw call, is not executed as program control transfers to where the exception is handled–catch block.
Catch: Now that we know from where and how exceptions can be thrown, let’s see how these exceptions are dealt with, or handled. The corresponding terminology is to catch an exception.
Catch functions are declared after the try scope and take an instance of the exception class as an argument.
<?php
try {
// if some condition is met or not met ->
throw new Exception("I am the error message!");
// below lines are not executed ->
echo "I am not executed!";
}
catch(Exception $e) {
echo "n Exception caught - ", $e->getMessage();
}
?>
OUTPUT
Exception caught - I am the error message!
If thrown errors are not caught (handled), they will manifest as an exception error.
<?php
try {
// if some condition is met or not met ->
throw new Exception("I am the error message!");
// below lines are not executed ->
echo "I am not executed!";
}
?>
OUTPUT
PHP Fatal error: Cannot use try without catch or finally in /workspace/Main.php on line 4
Multiple catch functions can also be used to deal differently with various types of exceptions thrown from a single try block.
<?php
try {
// throwing error(s)
}
// catching a generic exception
catch (Exception $e) {
echo $e->getMessage();
}
// catching a specific type of exception
catch (RangeException $e) {
echo $e->getMessage();
}
?>
Finally: The finally block is used for cleaning up any resources, like closing a database connection, disconnecting from a network, or closing a file reader. After an exception is thrown from the try block, control is transferred to the catch function, where it is accordingly dealt with.
Finally blocks execute code at the end of all the exception handling. Regardless of whether an exception was thrown or caught (or neither!), finally blocks will be executed after the try, and catch blocks before the normal execution continues.
<?php
try {
// if some condition is met or not met ->
throw new Exception("I am the error message!");
// below lines are not executed ->
echo "I am not executed!";
}
catch(Exception $e) {
echo "n Exception caught - ", $e->getMessage();
}
finally {
echo "n I am executed in the end";
}
?>
Exception caught - I am the error message!
I am executed in the end
Based on if an error is thrown or not, there are two types of sequence instructions. In either case, the finally block is executed at the end.
Let’s put together our understanding of these basics using the blood donation center registration website example we discussed above by examining what happens when a person below the age of 18 tries to register for the center.
<?php
$name = "John Doe";
$age = 17;
try {
if ($age < 18 || $age > 65) {
throw new Exception("Only people between the ages of 18 and 65 are allowed to register as donors.");
}
start_registration($name, $age);
}
catch (Exception $e) {
echo "n Registration failed - ", $e->getMessage();
}
finally {
echo "n Please try again.";
}
?>
OUTPUT:
Registration failed - Only people between the ages of 18 and 65 are allowed to register as donors.
Please try again.
Multiple Exceptions
We can work with multiple exceptions by informally breaking them down into two categories: sequenced exceptions (handling one exception after another), and nested exceptions (try blocks inside try blocks).
Sequenced Exceptions
In our blood center scenario, after checking if the age of the donor is valid, we can also impose some constraints for the name of the registering candidate. Any name input with less than two characters should be invalid, and should therefore trigger an error. We can handle exceptions for age and name, one after the other, as shown below:
<?php
$age = 17;
$name = "J";
try {
if ($age < 18 || $age > 65) {
throw new Exception("Only people between the ages of 18 and 65 are allowed to register as donors.");
}
}
catch (Exception $e) {
echo "n Ineligible to donate - ", $e->getMessage();
}
try {
if (strlen($name) < 2) {
throw new Exception("Name should contain at least 2 characters.");
}
}
catch (Exception $e) {
echo "n Invalid name - ", $e->getMessage();
}
?>
OUTPUT:
Registration failed - Only people between the ages of 18 and 65 are allowed to register as donors.
Invalid name - Name should contain at least 2 characters.
Nested Exceptions
There could be a requirement where we want to nest try blocks in a way that allows us to handle exceptions at higher or lower levels. We can rearrange the above implementation to accommodate for the different layers of abstraction:
<?php
$age = 17;
$name = "J";
try {
try {
if ($age < 18 || $age > 65) {
throw new Exception("Only people between the ages of 18 and 65 are allowed to register as donors.");
}
}
catch (Exception $e) {
echo "n Ineligible to donate - ", $e->getMessage();
}
if (strlen($name) < 2) {
throw new Exception("Name should contain at least 2 characters.");
}
}
catch (Exception $e) {
echo "n Invalid name - ", $e->getMessage();
}
?>
OUTPUT:
Registration failed - Only people between the ages of 18 and 65 are allowed to register as donors.
Invalid name - Name should contain at least 2 characters.
Based on our requirement, we can also throw exceptions from inside catch blocks as long as the catch block is part of a higher level try block.
<?php
try {
try {
// if something ->
throw("Error!");
}
catch (Exception $e) {
echo $e->getMessage();
// if something ->
throw("Error - from inside the catch block");
}
}
catch (Exception $e) {
echo $e->getMessage();
}
?>
Custom Exception Classes
You can also create your own custom exceptions that cater to an application-specific requirement. Custom exceptions extend from the inherent exception class, so they have the same existing properties and functionalities, like information about the file and the corresponding line number of the error. Each exception class has an errorMessage() function that can be overridden to declare appropriate error messages for the corresponding custom exception. This is how you can create a custom exception in PHP:
<?php
class myException extends Exception {
// overriding for custom error message
public function errorMessage() {
//error message
$errorMessage = 'Error on line number '.$this->getLine().' in file - '.$this->getFile().': <br><b>'.$this->getMessage().'</b> is not an eligible age to donate blood. Only people between the ages of
18 and 65 are allowed to register as donors.';
return $errorMessage;
}
}
$name = "John Doe";
$age = 17;
try {
if ($age < 18 || $age > 65) {
// throwing custom exception with age parameter
throw new myException($age);
}
start_registration($name, $age);
}
catch (myException $e) {
//display custom message
echo "n Registration failed - ", $e->errorMessage();
}
finally {
echo "n Please try again.";
}
?>
OUTPUT:
Registration failed - Error on line number 18 in file - /workspace/Main.php:
17 is not an eligible age to donate blood. Only people between the ages of 18 and 65 are allowed to register as donors.
Please try again.
Global Exception Handler
It is always likely that you didn’t cover the possibility of an exception. To be on the safe side, it’s good to have a global, top-level function that takes care of any exceptions that you weren’t expecting. This function will help you deal with exceptions that are not included in try blocks. PHP allows us to define such an exception handler using the in-built set_exception_handler
function, which takes as an argument the name of the function that is responsible for catching this unexpected exception:
function global_exception_handler($exception) {
echo "Exception:" . $exception->getMessage();
}
set_exception_handler('global_exception_handler');
Third Party Frameworks
There are many third party error handling frameworks, like Whoops, Booboo, and Lagger that make exception handling for PHP easy. With neat interfaces and elaborate logging mechanisms, debugging is a lot simpler thanks to these frameworks.
Whoops is one of the more popular error handling frameworks used by PHP developers. You can see a demo of what an exception looks like with Whoops here.
Let’s see how we can integrate Whoops into our PHP projects.
Installing Whoops
Whoops can be installed into your project using Composer, which is a cross-platform dependency manager for PHP.
composer require filp/whoops
Instantiating Whoops:
$whoops = new WhoopsRun;
Whoops Handlers
Whoops ships with five in-built handlers, allowing you to log the error messages in whichever format you like:
- PrettyPageHandler: for nice looking error pages
- PlainTextHandler: for plain text error messages
- CallbackHandler: for custom callback functions; it takes the name of the callback function that is called when an exception is thrown as an argument.
- JsonResponseHandler: for JSON-formatted error messages
- XmlResponseHandler: for XML-formatted error messages
We can register a handler like this:
$whoops->prependHandler(new WhoopsHandlerPrettyPageHandler);
$whoops->register();
Now that we have the basics clear, let’s put all of this together and create some errors.
<?php
require "vendor/autoload.php";
$whoops = new WhoopsRun;
$whoops->prependHandler(new WhoopsHandlerPrettyPageHandler);
$whoops->register();
throw Exception("I am the error message!");
?>
This is what the interface looks like:
You can see that this provides an elaborate representation of the state of the script. In basic cases like our blood donation center example, these details may be unnecessary. However, for PHP developers working with API requests, cookies and session-based authentication, all of this information is very useful for debugging.
Now, compare this to what a conventional PHP error page looks like:
Fatal Error: Uncaught error: Call to undefined function Exception() in /opt/lampp/htdocs/test/hello.php:6 Stack trace #0 {main} thrown in /opt/lampp/htdocs/test/hello.php on line 6 |
It’s clear how third party libraries like Whoops can empower developers, making it easier to understand, handle and debug exceptions.
Wrapping it up
In this blog post, we covered exception handling and how to implement it. We also talked about handling multiple exceptions, creating your own, and using a third party exception handler.
All in all, we learned that it’s important to always be prepared for unexpected situations that require exceptions. To do this, we can wrap our code in try blocks, have appropriate catch functions to deal with the thrown errors and have a global exception handler that takes care of anything that you forgot to cover. Now that you have all the information, go forth and create your own exceptions!
Try ScoutAPM
Are you interested in trying out our new PHP monitoring agent? If so, then sign up here today for a free trial (no credit card necessary) or contact us with any queries you might have.