In this article we are going to learn about PHP Exceptions from ground up. These concepts are utilized in many large, scalable and object oriented applications and frameworks. Take advantage of this language feature to improve your skills as a web application developer.
1 An Example First
Before we begin with all the explanations, I would like to show an example first.
Let's say you want to calculate the area of a circle, by the given radius. This function will do that:
1 |
|
2 |
function circle_area($radius) { |
3 |
|
4 |
return pi() * $radius * $radius; |
5 |
|
6 |
}
|
It is very simple, however it does not check if the radius is a valid number. Now we are going to do that, and throw an exception if the radius is a negative number:
1 |
|
2 |
function circle_area($radius) { |
3 |
|
4 |
// radius can't be negative
|
5 |
if ($radius < 0) { |
6 |
throw new Exception('Invalid Radius: ' . $radius); |
7 |
} else { |
8 |
return pi() * $radius * $radius; |
9 |
}
|
10 |
|
11 |
}
|
Let's see what happens when we call it with a negative number:
1 |
|
2 |
$radius = -2; |
3 |
|
4 |
echo "Circle Radius: $radius => Circle Area: ". |
5 |
circle_area($radius) . "\n"; |
6 |
|
7 |
|
8 |
echo "Another line"; |
The script crashes with the following message:
1 |
|
2 |
<br /> |
3 |
<b>Fatal error</b>: Uncaught exception 'Exception' with message 'Invalid Radius: -2' in C:\wamp\www\test\test.php:19 |
4 |
Stack trace: |
5 |
#0 C:\wamp\www\test\test.php(7): circle_area(-2) |
6 |
#1 {main} |
7 |
thrown in <b>C:\wamp\www\test\test.php</b> on line <b>19</b><br /> |
Since it was a fatal error, no more code execution happened after that. However you may not always want your scripts to stop whenever an Exception happens. Luckily, you can 'catch' them and handle them.
This time, let's do it an array of radius values:
1 |
|
2 |
$radius_array = array(2,-2,5,-3); |
3 |
|
4 |
foreach ($radius_array as $radius) { |
5 |
|
6 |
try { |
7 |
echo "Circle Radius: $radius => Circle Area: ". |
8 |
circle_area($radius) . "\n"; |
9 |
} catch (Exception $e) { |
10 |
echo 'Caught Exception: ', $e->getMessage(), "\n"; |
11 |
}
|
12 |
|
13 |
}
|
Now we get this output:
1 |
|
2 |
Circle Radius: 2 => Circle Area: 12.566370614359 |
3 |
Caught Exception: Invalid Radius: -2 |
4 |
Circle Radius: 5 => Circle Area: 78.539816339745 |
5 |
Caught Exception: Invalid Radius: -3 |
There are no more errors, and the script continues to run. That is how you catch exceptions.
Full Screencast

2 What is an Exception?
Exceptions have been around in other object oriented programming languages for quite some time. It was first adopted in PHP with version 5.
By definition an Exception is 'thrown', when an exceptional event happens. This could be as simple as a 'division by zero', or any other kind of invalid situation.
1 |
|
2 |
throw new Exception('Some error message.'); |
This may sound similar to other basic errors that you have already seen many times. But Exceptions have a different kind of mechanism.
Exceptions are actually objects and you have the option to 'catch' them and execute certain code. This is done by using 'try-catch' blocks:
1 |
|
2 |
try { |
3 |
|
4 |
// some code goes here
|
5 |
// which might throw an exception
|
6 |
|
7 |
} catch (Exception $e) { |
8 |
|
9 |
// the code here only gets executed
|
10 |
// if an exception happened in the try block above
|
11 |
|
12 |
}
|
We can enclose any code within a 'try' block. The following 'catch' block is used for catching any exception that might have been thrown from within the try block. The catch block never gets executed if there were no exceptions. Also, once an exception happens, the script immediately jumps to the catch block, without executing any further code.
Further in the article we will have more examples that should demonstrate the power and flexibility of using exceptions instead of simple error messages.
3 Exceptions Bubble Up
When an exception is thrown from a function or a class method, it goes to whoever called that function or method. And it keeps on doing this until it reaches the top of the stack OR is caught. If it does reach the top of the stack and is never called, you will get a fatal error.
For example, here we have a function that throws an exception. We call that function from a second function. And finally we call the second function from the main code, to demonstrate this bubbling effect:
1 |
|
2 |
function bar() { |
3 |
|
4 |
throw new Exception('Message from bar().'); |
5 |
|
6 |
}
|
7 |
|
8 |
function foo() { |
9 |
|
10 |
bar(); |
11 |
|
12 |
}
|
13 |
|
14 |
|
15 |
try { |
16 |
|
17 |
foo(); |
18 |
|
19 |
} catch (Exception $e) { |
20 |
echo 'Caught exception: ', $e->getMessage(), "\n"; |
21 |
}
|
So, when we call foo(), we attempt to catch any possible exceptions. Even though foo() does not throw one, but bar() does, it still bubbles up and gets caught at the top, so we get an output saying: "Caught exception: Message from bar()."
4 Tracing Exceptions
Since exceptions do bubble up, they can come from anywhere. To make our job easier, the Exception class has methods that allows us to track down the source of each exception.
Let's see an example that involves multiple files and multiple classes.
First, we have a User class, and we save it as user.php:
1 |
|
2 |
class User { |
3 |
|
4 |
public $name; |
5 |
public $email; |
6 |
|
7 |
public function save() { |
8 |
|
9 |
$v = new Validator(); |
10 |
|
11 |
$v->validate_email($this->email); |
12 |
|
13 |
// ... save
|
14 |
echo "User saved."; |
15 |
|
16 |
return true; |
17 |
}
|
18 |
|
19 |
}
|
It uses another class named Validator, which we put in validator.php:
1 |
|
2 |
class Validator { |
3 |
|
4 |
public function validate_email($email) { |
5 |
|
6 |
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { |
7 |
throw new Exception('Email is invalid'); |
8 |
}
|
9 |
|
10 |
}
|
11 |
|
12 |
}
|
From our main code, we are going to create a new User object, set the name and email values. Once we call the save() method, it will utilize the Validator class for checking the email format, which might return an exception:
1 |
|
2 |
include('user.php'); |
3 |
include('validator.php'); |
4 |
|
5 |
|
6 |
$u = new User(); |
7 |
$u->name = 'foo'; |
8 |
$u->email = '$!%#$%#*'; |
9 |
|
10 |
$u->save(); |
However, we would like to catch the exception, so there is no fatal error message. And this time we are going to look into the detailed information about this exception:
1 |
|
2 |
include('user.php'); |
3 |
include('validator.php'); |
4 |
|
5 |
try { |
6 |
|
7 |
$u = new User(); |
8 |
$u->name = 'foo'; |
9 |
$u->email = '$!%#$%#*'; |
10 |
|
11 |
$u->save(); |
12 |
|
13 |
} catch (Exception $e) { |
14 |
|
15 |
echo "Message: " . $e->getMessage(). "\n\n"; |
16 |
echo "File: " . $e->getFile(). "\n\n"; |
17 |
echo "Line: " . $e->getLine(). "\n\n"; |
18 |
echo "Trace: \n" . $e->getTraceAsString(). "\n\n"; |
19 |
|
20 |
}
|
The code above produces this output:
1 |
|
2 |
Message: Email is invalid |
3 |
|
4 |
File: C:\wamp\www\test\validator.php |
5 |
|
6 |
Line: 7 |
7 |
|
8 |
Trace: |
9 |
#0 C:\wamp\www\test\user.php(11): Validator->validate_email('$!%#$%#*') |
10 |
#1 C:\wamp\www\test\test.php(12): User->save() |
11 |
#2 {main} |
So, without looking at a single line of code, we can tell where the exception came from. We can see the file name, line number, exceptions message and more. The trace data even shows the exact lines of code that got executed.
The structure of the default Exception class is shown in the PHP manual, where you can see all the methods and data it comes with:



5 Extending Exceptions
Since this is an object oriented concept and Exception is a class, we can actually extend it to create our own custom exceptions.
For example you may not want to display all the details of an exception to the user. Instead, you can display a user friendly message, and log the error message internally:
1 |
|
2 |
// to be used for database issues
|
3 |
class DatabaseException extends Exception { |
4 |
|
5 |
// you may add any custom methods
|
6 |
public function log() { |
7 |
|
8 |
// log this error somewhere
|
9 |
// ...
|
10 |
}
|
11 |
}
|
12 |
|
13 |
// to be used for file system issues
|
14 |
class FileException extends Exception { |
15 |
|
16 |
// ...
|
17 |
|
18 |
}
|
We just created two new types of exceptions. And they may have custom methods.
When we catch the exception, we can display a fixed message, and call the custom methods internally:
1 |
|
2 |
function foo() { |
3 |
|
4 |
// ...
|
5 |
// something wrong happened with the database
|
6 |
throw new DatabaseException(); |
7 |
|
8 |
}
|
9 |
|
10 |
|
11 |
try { |
12 |
|
13 |
// put all your code here
|
14 |
// ...
|
15 |
|
16 |
foo(); |
17 |
|
18 |
} catch (FileException $e) { |
19 |
|
20 |
die ("We seem to be having file system issues. |
21 |
We are sorry for the inconvenience."); |
22 |
|
23 |
} catch (DatabaseException $e) { |
24 |
|
25 |
// calling our new method
|
26 |
$e->log(); |
27 |
|
28 |
// exit with a message
|
29 |
die ("We seem to be having database issues. |
30 |
We are sorry for the inconvenience."); |
31 |
|
32 |
} catch (Exception $e) { |
33 |
|
34 |
echo 'Caught exception: '. $e->getMessage(). "\n"; |
35 |
|
36 |
}
|
This is the first time we are looking at an example with multiple catch blocks for a single try block. This is how you can catch different kinds of Exceptions, so you can handle them differently.
In this case we will catch a DatabaseException and only that catch block will get executed. In this blog we may call our new custom methods, and display a simple message to the user.
Please note that the catch block with the default Exception class must come last, as our new child classes are also still considered that class. For example 'DatabaseException' is also considered 'Exception' so it can get caught there if the order is the other way around.
6 Handling Uncaught Exceptions
You may not always want to look for exceptions in all of your code, by wrapping everything in try-catch blocks. However, uncaught exceptions display a detailed error message to the user, which is also not ideal in a production environment.
There is actually a way to centralize the handling of all uncaught exceptions so you can control the output from a single location.
For this, we are going to be utilizing the set_exception_handler() function:
1 |
|
2 |
set_exception_handler('exception_handler'); |
3 |
|
4 |
function exception_handler($e) { |
5 |
|
6 |
// public message
|
7 |
echo "Something went wrong.\n"; |
8 |
|
9 |
// semi-hidden message
|
10 |
echo "<!-- Uncaught exception: " . $e->getMessage(). " -->\n"; |
11 |
|
12 |
}
|
13 |
|
14 |
|
15 |
throw new Exception('Hello.'); |
16 |
throw new Exception('World.'); |
The first line instructs PHP to call a given function when an exception happens and is not caught. This is the output:
1 |
|
2 |
Something went wrong. |
3 |
<!-- Uncaught exception: Hello. -->
|
As you can see the script aborted after the first exception and did not execute the second one. This is the expected behavior of uncaught exceptions.
If you want your script to continue running after an exception, you would have to use a try-catch block instead.
7 Building a MySQL Exception Class
We are going to finish off this tutorial by building a custom MySQL Exception class that has some useful features, and see how we can use it.
1 |
|
2 |
class MysqlException extends Exception { |
3 |
|
4 |
// path to the log file
|
5 |
private $log_file = 'mysql_errors.txt'; |
6 |
|
7 |
|
8 |
public function __construct() { |
9 |
|
10 |
$code = mysql_errno(); |
11 |
$message = mysql_error(); |
12 |
|
13 |
// open the log file for appending
|
14 |
if ($fp = fopen($this->log_file,'a')) { |
15 |
|
16 |
// construct the log message
|
17 |
$log_msg = date("[Y-m-d H:i:s]") . |
18 |
" Code: $code - " . |
19 |
" Message: $message\n"; |
20 |
|
21 |
fwrite($fp, $log_msg); |
22 |
|
23 |
fclose($fp); |
24 |
}
|
25 |
|
26 |
// call parent constructor
|
27 |
parent::__construct($message, $code); |
28 |
}
|
29 |
|
30 |
}
|
You may notice that we put pretty much all of the code in the contructor. Whenever an exception is thrown, it is like creating a new object, which is why the constructor is always called first. At the end of the constructor we also make sure to call the parent constructor.
This exception will be thrown whenever we encounter a MySQL error. It will then fetch the error number, and the message directly from mysql, and then store that information in a log file, along with the timestamp. In our code we can catch this exception, display a simple message to the user, and let the exception class handle the logging for us.
For example, let's try to connect to MySQL without providing any user/password information:
1 |
|
2 |
try { |
3 |
|
4 |
// attempt to connect
|
5 |
if (!@mysql_connect()) { |
6 |
throw new MysqlException; |
7 |
}
|
8 |
|
9 |
} catch (MysqlException $e) { |
10 |
|
11 |
die ("We seem to be having database issues. |
12 |
We are sorry for the inconvenience."); |
13 |
|
14 |
}
|
We need to prepend the error suppression operator (@) before the mysql_connect() call so that it does not display the error to the user. If the function fails, we throw an exception, and then catch it. Only our user friendly message will be show to the browser.
The MysqlException class takes care of the error logging automatically. When you open the log file, you will find this line:
1 |
|
2 |
[2010-05-05 21:41:23] Code: 1045 - Message: Access denied for user 'SYSTEM'@'localhost' (using password: NO) |
Let's add more code to our example, and also provide a correct login info:
1 |
|
2 |
try { |
3 |
|
4 |
// connection should work fine
|
5 |
if (!@mysql_connect('localhost','root','')) { |
6 |
throw new MysqlException; |
7 |
}
|
8 |
|
9 |
// select a database (which may not exist)
|
10 |
if (!mysql_select_db('my_db')) { |
11 |
throw new MysqlException; |
12 |
}
|
13 |
|
14 |
// attempt a query (which may have a syntax error)
|
15 |
if (!$result = mysql_query("INSERT INTO foo SET bar = '42 ")) { |
16 |
throw new MysqlException; |
17 |
}
|
18 |
|
19 |
|
20 |
} catch (MysqlException $e) { |
21 |
|
22 |
die ("We seem to be having database issues. |
23 |
We are sorry for the inconvenience."); |
24 |
|
25 |
}
|
If the database connection succeeds, but the database named 'my_db' is missing, you will find this in the logs:
1 |
|
2 |
[2010-05-05 21:55:44] Code: 1049 - Message: Unknown database 'my_db' |
If the database is there, but the query fails, due to a syntax error for example, you may see this in the log:
1 |
|
2 |
[2010-05-05 21:58:26] Code: 1064 - Message: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''42' at line 1 |
Bonus Building a DB Class with PHP Magic
We can make the code example above even cleaner by writing our own database class, which handles the throwing of the exceptions. This time I am going to be using some 'magic' features of PHP to construct this class.
At the end, we want our main code to look like this:
1 |
|
2 |
try { |
3 |
|
4 |
Database::connect('localhost','root',''); |
5 |
|
6 |
Database::select_db('test'); |
7 |
|
8 |
$result = Database::query("INSERT INTO foo SET bar = '42 "); |
9 |
|
10 |
} catch (MysqlException $e) { |
11 |
|
12 |
die ("We seem to be having database issues. |
13 |
We are sorry for the inconvenience."); |
14 |
|
15 |
}
|
It is nice and clean. We do not check for any errors in every single database call. This new Database class has the responsibility to throw exceptions when errors happen. And since these exceptions do bubble up, they get caught by our catch block at the end.
And here is the magic Database class:
1 |
|
2 |
class Database { |
3 |
|
4 |
// any static function call invokes this
|
5 |
public static function __callStatic($name, $args) { |
6 |
|
7 |
// function to be called
|
8 |
$function = 'mysql_' . $name; |
9 |
|
10 |
// does the function exist?
|
11 |
if (!function_exists($function)) { |
12 |
// throw a regular exception
|
13 |
throw new Exception("Invalid mysql function: $function."); |
14 |
}
|
15 |
|
16 |
// call the mysql function
|
17 |
$ret = @call_user_func_array($function , $args); |
18 |
|
19 |
// they return FALSE on errors
|
20 |
if ($ret === FALSE) { |
21 |
// throw db exception
|
22 |
throw new MysqlException; |
23 |
}
|
24 |
|
25 |
// return the returned value
|
26 |
return $ret; |
27 |
}
|
28 |
|
29 |
}
|
It only has one method, and it gets invoked whenever we call a static method on this class. You may read more on this concept here: PHP Overloading.
So, when we call Database::connect(), this code automatically calls mysql_connect(), passes the arguments, checks for errors, throws exceptions when needed, and returns the returned value from the function call. It basically acts like a middle man, and handles the dirty work.
Conclusion
I hope you enjoyed this tutorial and learned from it. Now you should have a better grasp of this subject. Try and see if you can utilize PHP Exceptions in your next project. See you next time!