Notices:
These
are trivial, non-critical errors that PHP encounters while executing a script
Example: Accessing a variable that has not yet been
defined. By default, such errors are not displayed to the user at all - although
you can change this default behavior.
Warnings:
These
are more serious errors
Example:
attempting to include() a file which does not exist. By default, these errors
are displayed to the user, but they do not result in script termination.
Fatal errors:
These
are critical errors
Example: Instantiating
an object of a non-existent class, or calling a non-existent function. These
errors cause the immediate termination of the script, and PHP’s default behavior
is to display them to the user when they take place.
Parse Error:
When
we make mistake in PHP code like, missing semicolon or any unexpected symbol in
code
What are Exceptions?
Exceptions are
the PHP 5 way of flagging up an unexpected event. They contain an information
message, and can be extended just like any other object. Exceptions are thrown
rather than warnings in newer PHP extensions, although you do still see
extensions returning errors.
When an exception is thrown, we either catch it where it happens, or it causes the code to return to its calling context. If it isn’t caught there, it returns again, and so on. As usual it is easier to explain with examples than words!
When an exception is thrown, we either catch it where it happens, or it causes the code to return to its calling context. If it isn’t caught there, it returns again, and so on. As usual it is easier to explain with examples than words!
Here is an
example in which DateTimeZone objects are created, first with a valid
timezone string of ‘Europe/Amsterdam’, then another with an invalid timezone
‘nonsense’:
$timezone = new DateTimeZone('Europe/Amsterdam');
var_dump($timezone);
$timezone2 = new DateTimeZone('nonsense');
As the documentation
explains, DateTimeZone throws an exception when called with
unexpected data. This indeed happens if you run the above script, and the
exception appears as an error message from PHP, looking something like this:
Exception: DateTimeZone::__construct(): Unknown or bad timezone
(nonsense)
in /...../exception1.php on line 6
We don’t want our entire application to stop and spit out errors over a nonsense timezone, and working with exceptions means we can do so much more with these situations than we can with errors. We “catch” exceptions and can use the information in the exception itself and in the context of the current scope to decide how best to react. This is a great improvement on the previous situation of having a few different error levels and only being able to choose whether to display, log or ignore each level as was the situation before PHP 5.
Handling Exceptions
We handle
exceptions with a try/catch block like this:
try {
$timezone2 =
new DateTimeZone('nonsense');
echo
"time!";
} catch(Exception $e) {
echo
"Oops! Something bad
happened!";
print_r($e);
}
The “try” bit
goes around the code that you would normally write, but which contains
operations which can cause an exception. The “catch” bit states what kind of
exception to catch, and contains the code to execute if we do catch anything.
If you ran this
script with a valid timezone as the argument to
the DateTimeZone constructor, you would see the “time!” output and
never enter the code inside the “catch” block. However in our example, an
exception will be caused and so we jump straight into this block, without
echoing “time!”. This is a fairly key concept – the code in the try block
actually stops and we jump into the catch block and start executing from there.
Any code after the point at which the exception is throw simply isn’t run.
Inside an Exception
Exceptions can
hold all sorts of information. If we inspect our Exception thrown by
DateTimeZone’s constructor using print_r, we’d see something like this:
Exception Object
(
[message:protected] => DateTimeZone::__construct(): Unknown or bad
timezone (nonsense)
[string:private] =>
[code:protected] => 0
[file:protected] =>
/home/lorna/data/personal/publish/thinkvitamin/exceptions/exceptions2.php
[line:protected]
=> 4
[trace:private]
=> Array
(
[0]
=> Array
(
[file] =>
/home/lorna/data/personal/publish/thinkvitamin/exceptions/exceptions2.php
[line] => 4
[function] => __construct
[class] => DateTimeZone
[type] => ->
[args] => Array
(
[0] => nonsense
)
)
)
[severity]
=> 2
)
Walking through
the object, there are many useful elements here:
message This
contains a useful and relevant information message
code By
default this is zero but we can use this to include a numeric error code with
our exception
file Path
to the file where this exception was thrown
line Line
number where the exception was thrown
trace This
is the stack trace, so you can see the stack of calls which were made before
this exception was thrown
This is all
valuable information and we can use it to inform how we handle errors in the
catch block in our code. In addition we’ll take a look at how we can set these
ourselves by making our own exceptions a little later on.
Throwing Exceptions
It isn’t only
PHP itself that can throw exceptions, we can use exactly the same functionality
in our own code. Here is a really simple example, taken directly from the PHP manual itself,
of how to throw an exception manually.
function inverse($x) {
if (!$x) {
throw new
Exception('Division by zero.');
}
else return
1/$x;
}
We simply throw
a new Exception object that we create as we need it. The string
passed into the constructor becomes the object’s message property, and we can
also pass a second argument to the constructor if we want to set the code
property as well. It is usual to throw exceptions in functions or methods, and
then catch them from the location they are called – this lets the calling code
decide what to do in the context of what it intended and is much more flexible
than simple errors or having your methods return false and set an error string
somewhere, which I’m sure is how I solved this before exceptions were
introduced in PHP 5.
Extending Exceptions
The Exception
object by itself is great, but we can extend it so that we can return
additional information and different types of exceptions – so if one of a
number of different scenarios actually happens, we can respond differently.
Consider this script and in particular the takeTwoNumbers function:
class BigNumberException extends Exception { }
class FavouriteNumberException extends Exception { }
function takeTwoNumbers($a, $b) {
if($a > 15
|| $b > 15 || ($a + $b) > 20) {
throw new
BigNumberException('Keep it simple with smaller numbers');
} elseif($a !=
3 && $b != 3) {
throw new
FavouriteNumberException('But three is my favourite number!');
} else {
return $a +
$b;
}
}
try {
echo
takeTwoNumbers(3,9) . "\n";
echo
takeTwoNumbers(4,16) . "\n";
echo
takeTwoNumbers(7,5) . "\n";
} catch(Exception $e) {
echo
$e->getMessage() . "\n";
}
The function accepts
two numbers, checks the values, and if there are any problems, throws a
relevant exception. If the values are OK (and yes, this is a totally contrived
example, in an effort to illustrate the concept without any complicating
factors like the real world getting involved!) then the function simply adds
them together and returns them. Our calling script calls the function 3 times,
with different number sets. The first one executes fine and the numbers get
added together. However the second includes a large number and so an error is
thrown, causing our code to jump to the “catch” block, and so the third
function call is never made.
Although we
declared empty classes to extend Exception here and so only the class
name changed, you can do all sorts of things to help record the information you
need. We would set custom error messages here (remember the helpful error
message returned by DateTimeZone?), set error codes, or even set new
properties of our own. It depends on your application which of these is helpful
but having the custom exception classes makes it very nice and tidy to do so.
The differing class names let us detect what *kind* of a problem occured and
what we want to do in response. When we catch an exception, we can state what
kind of an exception we wanted to catch – so we can narrow ourselves down to
only dealing with a known situation. We can also use multiple catch blocks,
like this:
try {
echo
takeTwoNumbers(3,9) . "\n";
echo
takeTwoNumbers(4,16) . "\n";
echo
takeTwoNumbers(7,5) . "\n";
} catch(BigNumberException $e) {
echo "Big
Number: " . $e->getMessage() . "\n";
} catch(FavouriteNumberException $e) {
echo
"Favourite Number: " . $e->getMessage() . "\n";
}
In this example,
we can handle the two types of exception differently. If any other type of
exception were to occur, it wouldn’t be caught here, but instead would “bubble”
up the stack, getting returned from the end of the second catch block to
wherever this code was called from – in this very simple example we are in a simple
PHP file but if we were nested in functions, the code would keep on returning
until PHP caught the exception, or until it reached the top at which point it
would show an error like the one we saw at the start of this post.
Uncaught Exceptions
So if an
exception is thrown in a method, and not caught, it returns from the function
to where that function is called from. If it isn’t caught here, it returns to
where this code was called from, and so on until it reaches the top of the
stack. If it gets to the top of the stack, uncaught, we’ll see the error we saw
at the beginning of the post. We can also give PHP instructions about how to
handle any exceptions that get this far, using set_exception_handler.
Many frameworks will do this and return nicely formatted messages, often
logging the detail of the error so that users do not see it but developers can
do so.
Here’s an
example of a catch-all exception handling function:
function my_exception_handler($exception) {
error_log("Uncaught " . get_class($exception) . " exception:
" . $exception->getMessage() . "\n");
}
set_exception_handler('my_exception_handler');
throw new Exception('Oooops!');
All we need to
do is declare our exception handling function and then tell PHP to use it for
uncaught exceptions. This simple example simply passes a string to error_log function,
which you can configure to behave however you need it to for your system. In a
real-world application you might want to notify administrators of the problem
or perhaps provide some nice feedback to the user in addition to logging the
error.
Exceptions in Your Applications
Hopefully this
has given some insight into what you can do with exceptions and how they
interact with the rest of your object-oriented application. Even functional
code needs awareness of exceptions since some core language elements now throw
them, so it is worth making sure you have the exception handler declared
alongside your existing error handler even if you don’t need to be extending or
throwing exceptions yourself.
Comments
Post a Comment