Every once in a while you stumble upon a topic that you thought would have been completely covered. This I thought as well about PHP Errorhandling. Although resources are abundant about the topic, most of them cover Error handling without handling the Fatal Errors or with hacks for a solution.
This last problem can easily be solved as I wil explain in this post. We wil build an errorhandling class which starts trapping errors from the moment it comes into existance.
What comes first in such a task is to review what it must do, for this specific class we assume the following:
- We run PHP 5.2.0 or higher, this is needed for our little trick
- The class must be a singleton
- It must trap all possible errors, including fatal errors
- Once trapped, we can do custom handling with them and then pass the error to the defaulthandler
The class skeleton
Since we have stated as a requirement that our class should be a singleton, we will have to define the constructor as being private and create a getInstance method. The latter will instantiate the class the first time it is called.
Below is an example of how to do a singleton class skeleton:
class My_ErrorHandler { private function __construct() {} public static function getInstance() { static $Instance = null; if ($Instance === null) { $Instance = new My_ErrorHandler(); } return $Instance; } }
Although this post is not about Singletons in PHP I might as well explain a little about the code above.
In our new class we define the constructor as private, which effectively means that we can only instantiate this class from within and not from another class.
Protected would do as well and would perhaps be better because classes which extend this one can also call the constructor, but I leave this open to debate
And to round up we create a static method named getInstance (sounds logical?) which contains a static variable holding the instance. When this method is called we check whether the static variable contains our instance and if it does not, we instantiate is.
It really is that simple.
For the sake of completeness I must add that another way is to define our instance variable as a private static class variable instead of a method variable, this has the added advantage that other functions in the class could use it. I personally like to encapsulate it this way.
Errorhandling as you know it
Now that we have the basis for our errorhandling class, let’s create an error handling method as described in the PHP Manual. It is actually quite simple and I will quickly go over it.
All one has to do is define a method and register it to PHP with the set_error_handler call. A method needs to have 5 parameters:
- An error level, this gives an integer corresponding to the E_ error constants fo PHP, i.e. E_WARNING
- A message, here we receive the text accompanying the error
- A file name, this is the file in which the error was triggered
- A line number, this is the line number where the error occurred
- An errorcontext, this is an array containing all the variables and their values in the scope where and when the error occurred
Now we can choose, do we fully handle the error ourselves, including handling when to die the script or not?
This can be done by the return value of our method. If we return false, the default PHP error handler handles the error. And if you do not specify a return value PHP will believe you handled the complete error and continue executing where it was.
I usually return false since I want to catch the errors and do something extra, like send it to the Syslog or sending an e-mail in some cases.
Example:
public function error($level, $message, $file, $line, $ctx){ if (error_reporting() > 0) { // Do something here ... } return false; }
You might have noticed that I check whether error_reporting is larger than zero. This is because of the at(@) operator. If we do not check this we end up handling the errors which were supposed to be silenced via the @ command.
To wrap this part up, we insert the set_error_handler call to the constructor of our class. This makes sure that once the class has been instantiated, we immediately start catching errors.
By the way, if you intend to send output to for example a Syslog, make sure you tell PHP to give plain text message instead of HTML typed.
Should you intend to use assert statements, add ASSERT_WARNING to your assert options.
Another example showing all of the above:
private function __construct() { ini_set('html_errors', '0'); set_error_handler(array(&$this, 'error')); assert_options (ASSERT_WARNING, 0); }
Catching the fatal error
The above is all fun but well, it still does not catch the dreaded fatal error which let’s your application end up all white.
Well, this is what we are going to address now and is actually really simple once you know it (and you run PHP 5.2+, but you do that, right?)
With PHP 5.2.0 a new command was introduced, namely: error_get_last.
This function returns the last error which has occurred in PHP. Although it might not sound exciting, try combining it with register_shutdown_function.
If you register a shutdown function which calls error_get_last and checks if an error has occurred, it can call our previously defined error function with the given level, message, file and line. With this call we have automatically caught our fatal errors as well!
Let me show you:
function shutdown() { $error = error_get_last(); if ($error != null) { $this->error($error['type'], $error['message'], $error['file'], $error['line'], 0); exit; } }
And we call the register function in our constructor as well:
private function __construct() { // .. Things we have defined above .. register_shutdown_function(array(&$this, 'shutdown')); }
Conclusion
This is all there is to it, if you call My_Errorhandler::getInstance() at the beginning of your script then all your custom error handling will automatically be invoked.
You can use the power of this function to pro-actively handle errors instead of waiting for users to report them to you. Just couple this with your Syslog or auto-mail the development department and you will know when things go bad.
I know I had fun writing this, I hope you have by implementing this!