Programster's Blog

Tutorials focusing on Linux, programming, and open-source

PHP Error and Exception Handling

PHP

Bugs happen. It's pretty annoying when something goes wrong, but its even worse if administrators/developers aren't even aware of it. Here we will see how to register some handlers so that we can capture any kind of issue in our PHP application, giving us a chance to log it and alert the people who need to know.

This article is aimed at PHP 7.0 which handles errors differently to previous versions.

Setting An Error Handler

You can register an error handler with the set_error_handler function. This will run every time an error is raised with trigger_error, or a warning is raised from something like calling fopen() on a file that doesn't exist. Here is an example:

<?php

$errorHandler = function($errno, $errstr, $errfile, $errline, $errcontext){

    $output = array(
        'error_code' => $errno, 
        'error_string' => $errstr, 
        'file' => $errfile, 
        'line' => $errline
    );

    print "Error handler received error: " . print_r($output, true);
};

set_error_handler($errorHandler);

print "About to trigger error..." . PHP_EOL;
trigger_error("Some error message", E_USER_ERROR);
print "After error raised." . PHP_EOL;

... for which this is the output:

About to trigger error...
Error handler received error: Array
(
    [error_code] => 256
    [error_string] => Some error message
    [file] => /home/programster/php-error-handling/error-handler-example.php
    [line] => 18
)
After error raised.

As you can see, the triggering of the error immediately causes the error handler to execute, but the script will carry on, unlike with an exception.

Warnings Also Count

Please note that anything raised by trigger_error() causes the error handler to execute. This includes types other than error, such as a warning as shown below:

<?php

$errorHandler = function($errno, $errstr, $errfile, $errline, $errcontext){

    $output = array(
        'error_code' => $errno, 
        'error_string' => $errstr, 
        'file' => $errfile, 
        'line' => $errline
    );

    print "Error handler received error: " . print_r($output, true);
};

set_error_handler($errorHandler);

print "About to trigger warning..." . PHP_EOL;
trigger_error("Some warning message", E_USER_WARNING);
print "After warning raised." . PHP_EOL;
About to trigger warning...
Error handler received error: Array
(
    [error_code] => 512
    [error_string] => Some warning message
    [file] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/exception-handler.php
    [line] => 18
)
After warning raised.

Setting An Exception Handler

You can register a handler for uncaught exceptions with set_exception_handler(). Here is a basic example:

<?php

$exceptionHandler = function(Throwable $ex){
    print "Exception handler recieved exception: " . print_r($ex, true);
};

set_exception_handler($exceptionHandler);

print "About to throw exception..." . PHP_EOL;
throw new Exception("Raising exception");
print "After exception raised." . PHP_EOL;

Output:

About to throw exception...
Exception handler received exception: Exception Object
(
    [message:protected] => Raising exception
    [string:Exception:private] => 
    [code:protected] => 0
    [file:protected] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/exception-handler.php
    [line:protected] => 14
    [trace:Exception:private] => Array
        (
        )

    [previous:Exception:private] => 
)

Only Handles Uncaught Exceptions

Please be aware that this only captures uncaught exceptions that would otherwise terminate your application. It does not run for every exception that is raised in the application if they are caught. This is unlike the error handler which will execute for every triggered error (but errors aren't caught).

<?php
$exceptionHandler = function(Throwable $ex){
    print "Exception handler recieved exception: " . print_r($ex, true);
};

set_exception_handler($exceptionHandler);

print "About to throw exception that is caught..." . PHP_EOL;
try
{
    throw new Exception("Raising exception");
}
catch (Exception $e)
{
    print "Caught the exception." . PHP_EOL;
}

print "End of script." . PHP_EOL;
About to throw exception that is caught...
Caught the exception.
End of script.

Handles Some Errors Too

It appears that this will also capture errors that occur when you try to call a function that doesn't exist, like so:

<?php

$exceptionHandler = function(Throwable $ex){
    print "Exception handler recieved exception: " . print_r($ex, true);
};

set_exception_handler($exceptionHandler);

print "About to call function that doesnt exist..." . PHP_EOL;
someFuncThatDoesntExist();
print "End of script." . PHP_EOL;
About to call function that doesnt exist...
Exception handler recieved exception: Error Object
(
    [message:protected] => Call to undefined function someFuncThatDoesntExist()
    [string:Error:private] => 
    [code:protected] => 0
    [file:protected] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php
    [line:protected] => 10
    [trace:Error:private] => Array
        (
        )

    [previous:Error:private] => 
)

Fatal Errors and the Shutdown Handler

Even if you register an "error" handler, it will not capture fatal errors. Fatal errors can occur in situations where you:

  • run out of memory or exceed a memory limit.
  • exceed the execution time limit
  • Try to execute a function that doesn't exist.
  • Have an exception that isn't caught (but you can negate these by registering an exception handler).

Here is the example from the error handler section, but instead, we will cause a fatal error by tying to call someFuncThatDoesntExist() which hasn't been defined yet.

<?php

$errorHandler = function($errno, $errstr, $errfile, $errline, $errcontext){

    $output = array(
        'error_code' => $errno, 
        'error_string' => $errstr, 
        'file' => $errfile, 
        'line' => $errline
    );

    print "Error handler recieved error: " . print_r($output, true);
};

set_error_handler($errorHandler);

print "About to cause fatal error..." . PHP_EOL;
someFuncThatDoesntExist();
print "After fatal error caused." . PHP_EOL;

... which outputs:

About to cause fatal error...
PHP Fatal error:  Uncaught Error: Call to undefined function someFuncThatDoesntExist() in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/error-handler-example.php:18
Stack trace:
#0 {main}
  thrown in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/error-handler-example.php on line 18

As you can see, the script doesn't get to print the "After fatal error caused." line, and neither does the error handler get to execute.

To be able to capture and log fatal errors in your application, you have to create a shutdown handler.

The Shutdown Handler

You can register a function to run every time your application finishes, or is "shutting down". Here is a basic example:

<?php

$shutdownHandler = function(){
    print "Running the shutdown handler" . PHP_EOL;
};

register_shutdown_function($shutdownHandler);

print "Hello world." . PHP_EOL;

This outputs:

Hello world.
Running the shutdown handler

This seems pretty useless, except for that this function always runs, even when there is a fatal error as shown below:

<?php
$shutdownHandler = function(){
    print "Running the shutdown handler" . PHP_EOL;
};

register_shutdown_function($shutdownHandler);

print "About to cause fatal error..." . PHP_EOL;
someFuncThatDoesntExist();
print "After fatal error caused." . PHP_EOL;
About to cause fatal error...
PHP Fatal error:  Uncaught Error: Call to undefined function someFuncThatDoesntExist() in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php:16
Stack trace:
#0 {main}
  thrown in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php on line 16
Running the shutdown handler

As you can see, the fatal error is output, but then the shutdown handler is still executed. This gives us the chance to capture and log the error like so:

$shutdownHandler = function(){
    print PHP_EOL;
    print "============================" . PHP_EOL;
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();

    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // possibly log it here.
    }
    else
    {
        // normal shutdown without an error
        print "Running a normal shutdown without error." . PHP_EOL;
    }
};

register_shutdown_function($shutdownHandler);

print "About to cause fatal error..." . PHP_EOL;
someFuncThatDoesntExist();
print "After fatal error caused." . PHP_EOL;
About to cause fatal error...
PHP Fatal error:  Uncaught Error: Call to undefined function someFuncThatDoesntExist() in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php:30
Stack trace:
#0 {main}
  thrown in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php on line 30

=========================
Running the shutdown handler
Looks like there was an error: Array
(
    [type] => 1
    [message] => Uncaught Error: Call to undefined function someFuncThatDoesntExist() in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php:30
Stack trace:
#0 {main}
  thrown
    [file] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php
    [line] => 30
)

However, be aware that our shutdown handlers call to $error = error_get_last(); does not distinguish between a fatal error or just a "normal" one we trigger ourselves through trigger_error e.g.

<?php
$shutdownHandler = function(){
    print PHP_EOL;
    print "============================" . PHP_EOL;
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();

    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // possibly log it here.
    }
};

register_shutdown_function($shutdownHandler);

print "About to trigger warning..." . PHP_EOL;
trigger_error("warning! blah blah blah", E_USER_WARNING);
print "After warning raised." . PHP_EOL;
About to trigger warning...
PHP Warning:  warning! blah blah blah in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php on line 25
After warning raised.

============================
Running the shutdown handler
Looks like there was an error: Array
(
    [type] => 512
    [message] => warning! blah blah blah
    [file] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php
    [line] => 25
)

This can be pretty annoying if we are relying on our shutdown handler for just capturing the fatal errors. Luckily, if you define an error handler in addition to the shutdown handler, all the "normal" errors will be captured and handled by the error handler, and only the fatal errors will manage to get through to the shutdown handler as shown here:

$shutdownHandler = function(){
    print PHP_EOL;
    print "============================" . PHP_EOL;
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();

    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // possibly log it here.
    }
    else
    {
        print "Shutting down with no last error." . PHP_EOL;
    }
};

register_shutdown_function($shutdownHandler);

$errorHandler = function($errno, $errstr, $errfile, $errline, $errcontext){

    $output = array(
        'error_code' => $errno, 
        'error_string' => $errstr, 
        'file' => $errfile, 
        'line' => $errline
    );

    print "Error handler recieved error: " . print_r($output, true);
};

set_error_handler($errorHandler);

print "About to trigger warning..." . PHP_EOL;
trigger_error("warning! blah blah blah", E_USER_WARNING);
print "After warning raised." . PHP_EOL;
About to trigger warning...
Error handler recieved error: Array
(
    [error_code] => 512
    [error_string] => warning! blah blah blah
    [file] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php
    [line] => 43
)
After warning raised.

============================
Running the shutdown handler
Shutting down with no last error.

The same is true for exceptions and registering an exception handler as shown here:

<?php
$shutdownHandler = function(){
    print PHP_EOL;
    print "============================" . PHP_EOL;
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();

    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // possibly log it here.
    }
    else
    {
        print "Shutting down with no last error." . PHP_EOL;
    }
};

register_shutdown_function($shutdownHandler);

$exceptionHandler = function(Throwable $ex){
    print "Exception handler recieved exception: " . print_r($ex, true);
};

set_exception_handler($exceptionHandler);

print "About to throw exception..." . PHP_EOL;
throw new Exception("Raising exception");
print "End of script." . PHP_EOL;
About to throw exception...
Exception handler recieved exception: Exception Object
(
    [message:protected] => Raising exception
    [string:Exception:private] => 
    [code:protected] => 0
    [file:protected] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/shutdown-handler-example.php
    [line:protected] => 35
    [trace:Exception:private] => Array
        (
        )

    [previous:Exception:private] => 
)

============================
Running the shutdown handler
Shutting down with no last error.

Fatal Error - Max Execution Time

To cap it all off, here is an example script showing the registration of an exception handler, error handler, and shutdown handler to see what happens when you exceed your max execution time:

<?php

ini_set("max_execution_time", 10);

$shutdownHandler = function(){
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();

    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // possibly log it here.
    }
    else
    {
        // normal shutdown without an error
        print "Shutting down without error." . PHP_EOL;
    }
};

$errorHandler = function($errno, $errstr, $errfile, $errline, $errcontext){
    print "Running error handler..." . PHP_EOL;
};

$exceptionHandler = function(Throwable $exception) {
    print "running exception handler..." . PHP_EOL;
};

register_shutdown_function($shutdownHandler);
set_error_handler($errorHandler);
set_exception_handler($exceptionHandler);

print "Executing infinite loop that should surpass max execution time..." . PHP_EOL;
while (true)
{
    // do nothing and run forever.
}
Executing infinite loop that should surpass max execution time...
PHP Fatal error:  Maximum execution time of 10 seconds exceeded in /home/stuart/Seafile/Desktop/Desktop/php-error-handling/exception-handler.php on line 33
Running the shutdown handler
Looks like there was an error: Array
(
    [type] => 1
    [message] => Maximum execution time of 10 seconds exceeded
    [file] => /home/stuart/Seafile/Desktop/Desktop/php-error-handling/exception-handler.php
    [line] => 33
)

Conclusion

There was a lot to cover there but to keep things simple just follow these rules:

  • register an exception handler to capture any uncaught exceptions and errors that occur from trying to call code that doesn't exist.
  • register an error handler to capture any errors raised by trigger_error() or warnings raised from the standard library functions.
  • register a shutdown function and if you have fulfilled the previous two points, any errors returned by error_get_last will be for fatal errors for things like running out of memory, execution time, or something obscure that I haven't through of yet. "Stuff happens".

Now nothing can happen without you being alerted to it.

References

References