Still returning false whenever a function in your program fails? In this article, we’ll learn about PHP exceptions, and how you can use them to soup up your application’s error handling.

 


Introduction

What are exceptions?

Exceptions
Catching exceptions as shown by Superman
Image courtesy of http://tvtropes.org

Exceptions are special conditions, usually errors, that can occur or be explicitly made by a program. They connote that something entirely different from what was expected has occurred. In most cases, the situation requires that a special instruction set be done in order to mitigate a catastrophic system failure.

Uncaught Exception in a Nuclear Power plant
Uncaught exception in a nuclear power plant
Image courtesy of http://phenomenaonbreak.wordpress.com

How do we use exceptions?

Exceptions can be thrown and caught. When an exception is thrown, it means that something out of the ordinary has happened, and it will require another function to do something else. Catching an exception is the function telling the rest of the program, “Hey, I can handle this exception, let me at it!”

Come at me bro
Try-catch function: come at me bro
Image courtesy of http://richlington.blogspot.com

Exceptions can be explained by using the analogy of a baseball game.

  • The pitcher (Bowser) throws a baseball to the catcher.
  • The batter (Mario) tries to hit the ball. If he’s not able to do it, he gets a strike. If he’s able to do it, a fielder (either Donkey Kong or Waluigi) has to catch it.
  • If Donkey Kong catches the ball, he then has two choices: if he’s not close enough, he can throw it to another fielder; or if he’s close enough, he can throw it to a baseman and tag Mario to get an out.
Exceptions are like Mario Baseball. Wait, what?
Exceptions are like Mario Baseball. Wait, what?
Image courtesy of http://thebreakingstory.com

In exceptions, the pitcher throwing a baseball to the catcher is like a function running normally.

But when the batter hits the ball, something “exceptional” happens, and certain routines need to be done.

A fielder catching the baseball is like a try-catch function getting the exception. This try-catch function can now either handle the exception, or throw it for another try-catch function to handle. Whichever try-catch function can handle the exception is the one that finally does something.


Using PHP Exceptions

It’s really simple to implement exceptions in PHP. Since exceptions are part of the Standard PHP Library or SPL, they’re part of PHP core as long as you’re running PHP 5 and above. Exceptions are already there, just longing and waiting to be used.

Exceptions: Forever Alone
“Please use us.” Exceptions: forever alone.
Image courtesy of http://knowyourmeme.com

Exceptions are instantiated the same way as objects:

1
2
$exception = new Exception();
throw $exception;

Much like objects, they also have methods that can be called. These methods make it easier for a try-catch function to react to exceptions. For example, the getMessage() function will let you get an exception’s error message, which you can then log or show on the browser.

Here’s a list of an exception’s methods:

  • getMessage() – gets the exception’s message
  • getCode() – returns a numerical code that represents the exception
  • getFile() – returns the file where the exception was thrown
  • getLine() – returns the line number in the file where the exception was thrown
  • getTrace() – returns the an array of the backtrace() before the exception was thrown
  • getPrevious() – returns the exception thrown before the current one, if any
  • getTraceAsString() – returns the backtrace() of an exception as a string instead of an array
  • __toString() – returns the whole exception as a string. Is overrideable.

To better illustrate these methods, let’s create a User class that does basic user record management:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
class User
{
   protected $_user_id;
   protected $_user_email;
   protected $_user_password;
   public function __construct($user_id)
   {
      $user_record = self::_getUserRecord($user_id);
      $this->_user_id = $user_record['id'];
      $this->_user_email = $user_record['email'];
      $this->_user_password = $user_record['password'];
   }
   //we'll fill these in later
   public function __get($value) {}
   public function __set($name, $value) {}
   private static function _getUserRecord($user_id)
   {
      //For this tutorial, we'll use a mock-up record to represent retrieving the record
      //from the database and returning the data
      $user_record = array();
      switch($user_id) {
         case 1:
            $user_record['id'] = 1;
            $user_record['email'] = 'nikko@example.com';
            $user_record['password'] = 'i like croissants';
            break;
         case 2:
            $user_record['id'] = 2;
            $user_record['email'] = 'john@example.com';
            $user_record['password'] = 'me too!';
            break;
         case 'error':
            //simulates an unknown exception from whatever SQL library is being used:
            throw new Exception('An error from the SQL library!');
            break;
      }
      return $user_record;
   }
}
?>

In this example, there are several places where something exceptional could happen. For starters, the $user_id input in our _getUserRecord() function should be an integer. In the event that it’s not, we should throw an exception:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
...
...
private static function _getUserRecord($user_id)
{
   $user_id = self::_validateUserId($user_id);
   ...
   ...
   ...
}
private static function _validateUserId($user_id)
{
   if( !is_numeric($user_id) && $user_id != 'error' ) {
      throw new Exception('Whoa! I think there\'s something wrong with the user id');
   }
   return $user_id;
}

Let’s try it out by instantiating our User class with an invalid parameter:

1
2
3
4
5
//First try getting our user numero uno
$user = new User(1);
//Then, let's try to get the exception
$user2 = new User('not numeric');

You should get an error like this:

Exceptions: Forever Alone
Uncaught exception error message

Now, let’s have Donkey Kong catch this exception.

Exception caught
Exception caught, hell yeah!
Image courtesy of http://3×6.nl
1
2
3
4
5
6
7
8
9
try {
   //First try getting our user numero uno
   $user = new User(1);
   //Then, let's try to get the exception
   $user2 = new User('not numeric');
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception: {$e->getMessage()}";
}

Let’s run it again, this time with our try-catch function:

Thanks Donkey Kong!
Thanks Donkey Kong!

As you can see, with Donkey Kong catching our exceptions, we were able to suppress the awful error messages thrown by PHP when an exception is uncaught.

One thing to note here is that when an exception is thrown, it only stops when a try-catch function is able to catch it. Otherwise, it will remain uncaught, keep climbing up the trace of the function call, and be displayed as an error on the browser. In our current example, let’s trace where our exception gets thrown:

  • Create a new User object on the $user2 = new User('not numeric'); line
  • Run the __construct() function of the User class
  • Go to the _getUserRecord() function of the User class
  • Validate the $user_id using the _validateUserId function
  • If the $user_id is not numeric, throw a new Exception object

We can also run the getTraceAsString() function to get the trace of the exception, which will give us the exact same thing:

1
2
3
4
5
6
7
8
...
...
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception: {$e->getMessage()}";
   echo '<pre>';
   echo $e->getTraceAsString();
   echo '</pre>';
}
getTraceAsString
getTraceAsString() results

As you can see, even if the exception is thrown deep inside the trace of $user2 = new User('not numeric'), it keeps climbing up until it gets caught in the try-catch block on the top-most level.

Now, let’s construct our exception to use a code. Associating a code number with an exception is a good way of hiding the error from the client. By default, the code number of an exception is 0, but we can override this whenever we create a new exception:

1
2
3
4
5
6
7
8
9
...
...
...
if( !is_numeric($user_id) ) {
   throw new Exception('Whoa! I think there\'s something wrong with the user id', UserErrors::INVALIDID);
}
...
...
...

Don’t forget to create our UserErrors class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class UserErrors
{
   const INVALIDID      = 10001;
   const INVALIDEMAIL      = 10002;
   const INVALIDPW         = 10003;
   const DOESNOTEXIST      = 10004;
   const NOTASETTING    = 10005;
   const UNEXPECTEDERROR   = 10006;
   public static function getErrorMessage($code)
   {
      switch($code) {
         case self::INVALIDID:
            return 'Whoa! I think there\'s something wrong with the user id';
            break;
         case self::INVALIDEMAIL:
            return 'The email is invalid!';
            break;
         case self::INVALIDPW:
            return 'The password is shorter than 4 characters!';
            break;
         case self::DOESNOTEXIST:
            return 'That user does not exist!';
            break;
         case self::NOTASETTING:
            return 'That setting does not exist!';
            break;
         case self::UNEXPECTEDERROR:
         default:
            return 'An unexpected error has occurred';
            break;
      }
   }
}

We can then modify our exception handler to display the exception code instead of displaying the message:

1
2
3
4
5
...
...
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}";
}

It should result in this:

Exception Error Code

Now, let’s use all the other methods of an exception by trying to log the exception. First off, let’s create ourLogger class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Logger
{
   public static function newMessage(
      Exception $exception,
      $clear = false,
      $error_file = 'exceptions_log.html'
   ) {
      $message = $exception->getMessage();
      $code = $exception->getCode();
      $file = $exception->getFile();
      $line = $exception->getLine();
      $trace = $exception->getTraceAsString();
      $date = date('M d, Y h:iA');
      $log_message = "<h3>Exception information:</h3>
         <p>
            <strong>Date:</strong> {$date}
         </p>
         <p>
            <strong>Message:</strong> {$message}
         </p>
         <p>
            <strong>Code:</strong> {$code}
         </p>
         <p>
            <strong>File:</strong> {$file}
         </p>
         <p>
            <strong>Line:</strong> {$line}
         </p>
         <h3>Stack trace:</h3>
         <pre>{$trace}
         </pre>
         <br />
         <hr /><br /><br />";
      if( is_file($error_file) === false ) {
         file_put_contents($error_file, '');
      }
      if( $clear ) {
         $content = '';
      } else {
         $content = file_get_contents($error_file);
      }
      file_put_contents($error_file, $log_message . $content);
   }
}

We’ll have to modify our exception handler to use the Logger class:

1
2
3
4
5
6
...
...
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}";
   Logger::newMessage($e);
}

Now, when we run our program again, it should just display the exception code. We can then open theexceptions_log.html file and we’ll see the log of exceptions we’re getting:

Exception Log

Congratulations! You now have working knowledge on how to implement basic PHP Exceptions. Now, let’s move on to more advanced stuff!


Extending Exceptions

Even at their most basic level, exceptions are already an essential tool for error handling in applications. Byextending exceptions, however, we can create our own customized exceptions and take our application’s exception handling to the next level.

The Next Level
The next level is THIS much fun.

To create a custom exception, we’ll need to create our own exception class, extending the Exception class. Let’s create a custom UserException for our user class:

1
2
3
4
5
6
7
8
class UserException extends Exception
{
   public function __construct($error_code)
   {
      parent::__construct(UserErrors::getErrorMessage($error_code), $error_code);
      Logger::newMessage($this);
   }
}

As you can see, we overrode the __construct function to implement our own constructor. Since we extended the Exception object, we’re still able to use the default Exception constructor by using theparent keyword. By doing this, we can skip providing an error message every time we throw an exception, and just provide an exception code. We can also skip having to log it ourselves, since the custom constructor is set up to automatically log the exception.

Let’s update our code to use the new UserException class:

1
2
3
4
5
6
7
private static function _validateUserId($user_id)
{
   if( !is_numeric($user_id) && $user_id != 'error' ) {
      throw new UserException(UserErrors::INVALIDID);
   }
   return $user_id;
}

Since the exception already automatically logs itself, we can skip logging it in our try-catch block:

1
2
3
4
5
6
...
...
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}";
   //Logger::newMessage($e);
}

Let’s also update the other places in our User class where we can expect exceptions.

User class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
...
...
public function __get($value) {
   $value = "_{$value}";
   if( !isset($this->$value) ) {
      throw new UserException(UserErrors::NOTASETTING);
   }
   return $this->$value;
}
public function __set($name, $value) {
   switch($name) {
      case 'user_id':
         $user_id = self::_validateUserId($value);
         $this->_user_id = $user_id;
         break;
      case 'user_email':
         $user_email = self::_validateUserEmail($value);
         $this->_user_email = $user_email;
         break;
      case 'user_password':
         $user_password = self::_validateUserPassword($value);
         $this->_user_password = $user_password;
         break;
      default:
         throw new UserException(UserErrors::NOTASETTING);
         break;
   }
   return true;
}
private static function _getUserRecord($user_id)
{
   ...
   ...
   ...
   switch($user_id) {
      ...
      ...
      ...
      default:
         throw new UserException(UserErrors::DOESNOTEXIST);
         break;
   }
}

Now let’s try triggering these exceptions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = new User(1);
try {
   $user->user_email = 'invalid email';
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}<br />";
}
try {
   echo $user->setting_that_doesnt_exist;
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}<br />";
}
try {
   //user id that doesn't exist
   $user = new User(3);
} catch( Exception $e ) {
   echo "Donkey Kong has caught an exception with code: #{$e->getCode()}<br />";
}

We should get something like this:

Multiple exceptions

You’re probably wondering — besides custom constructors, what benefits can we get from custom exceptions? It would be relatively simple to just use the default Exception class. But by using a custom exception, we can:

  • Override the __construct function and implement our own
  • Organize our exceptions by the type of object they are
  • And the best one of all: Use multiple catch blocks and catch different kinds of exceptions depending on type!

Multiple Catch Blocks

Catch Me If You Can!
With multiple catch blocks, I will!
Image courtesy of http://movies.yahoo.com

By using multiple catch blocks, we are able to catch specific types of exceptions. This can be useful since we can indicate what to do when different kinds of exceptions occur. Currently, our try-catch functions catch the regular Exception type exceptions — this allows us to provide a fail-safe catch block for exceptions of types that have not been planned for.

With that in mind, let’s create a try-catch function that will catch our custom exception, UserException, and do something different about it. Let’s also use another catch block to make sure that any other unexpected exceptions that are thrown will still be caught.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
   $user = new User(1);
   $user->user_password = '123';
} catch( UserException $e ) {
   echo "Donkey Kong has caught a User Exception with code: #{$e->getCode()}<br />";
} catch( Exception $e ) {
   echo "Donkey Kong has caught a Exception with code: #{$e->getCode()}<br />";
}
try {
   //when we use 'error' as the argument, the User class will simulate an
   //unexpected exception from the SQL library
   $user = new User('error');
} catch( UserException $e ) {
   echo "Donkey Kong has caught a User Exception with code: #{$e->getCode()}<br />";
} catch( Exception $e ) {
   echo "Donkey Kong has caught a Exception with code: #{$e->getCode()}<br />";
}

We should see the following output:

Multiple Catch Blocks

We can clearly see that the UserException and the regular Exception were handled differently, since they have different messages. It may not seem useful right now, but imagine being able to log only certain exceptions, or even create a custom exception called DisplayableException, which we can set so that they’re the only ones that users will be able to see.


Conclusion

With the power of PHP exceptions, we can simply and easily handle different kinds of errors. Hopefully, with the help of this tutorial, you’ll be able to build from our simple example and create an even more effective way of handling your application’s errors through PHP exceptions.

Do you use exceptions in your applications? What kind of exceptions do you use? Let me know in the comments!

source: http://net.tutsplus.com/tutorials/php/the-ins-and-outs-of-php-exceptions/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+nettuts+%28Nettuts%2B%29

Print Friendly