Mayday, Mayday, Mayday - PHP Going Down

PHP provides a variety of tools for handling errors and exceptions, in particular extensible handlers for managing errors and exceptions as they occur. However, there is a range of fatal errors that PHP does not directly provide tools for you to handle them. In the best of scenarios, these fatal errors will result in partially outputted pages, or the “white screen of death.”

These errors include:

  • E_ERROR - Fatal run-time errors. These indicate errors that can not be recovered from, such as a memory allocation problem. Execution of the script is halted
  • E_PARSE - Compile-time parse errors. Parse errors should only be generated by the parser
  • E_CORE_ERROR - Fatal errors that occur during PHP’s initial startup. This is like an E_ERROR, except it is generated by the core of PHP
  • E_COMPILE_ERROR - Fatal compile-time errors. This is like an E_ERROR, except it is generated by the Zend Scripting Engine.

By leveraging a combination of shutdown function, error_get_last, and output buffering it is possible to put a structure in place that will allow you to trap fatal errors. The theory of operation is quite simple:

  1. Register a shutdown function.
  2. When the shutdown function is called, check to see if the script finished executing as expected by interrogating error_get_last for any fatal errors.
  3. Handle the fatal error as you see fit.

Let’s look at some code:

Bad Stuff Happend'; echo '

But that is okay

'; echo '' . print_r($error, true) . ''; } } } ob_end_flush(); ?>

The overall page generated is wrapped in an output buffer before being sent to the client. The use of output buffering here allows us to get rid of the partially generated content when the fatal error occurs and replace it with a custom error message. For example:

Bad Stuff Happend'; echo '

But that is okay

'; echo '' . print_r($error, true) . ''; } } } ?>

My Bad Day

What a bad day...

The failed require call generates a fatal E_COMPILE_ERROR. We trap the error, and instead of telling the client about “my bad day” we retract that and give the user an ever so comforting message that all is okay even though our website blew up. Instead of doing something smart like logging the error, we display it:

Bad Stuff Happend

But that is okay

Array ( [type] => 64 [message] => require() [function.require]: Failed opening required 'FileNotFound.php' (include_path='.:/usr/local/lib/php') [file] => /home/michael/www/eggplant/public_html/samples/fatal/index.php [line] => 28 )

If we fix the missing required file by adding the following script:

…we are able to catch the E_PARSE fatal error in our shutdown handler:

Bad Stuff Happend

But that is okay

Array ( [type] => 4 [message] => syntax error, unexpected T_STRING [file] => /home/michael/www/eggplant/public_html/samples/fatal/FileWithParseError.php [line] => 2 )

Let’s fix up that required file with some real code:

…Another caught E_ERROR:

Bad Stuff Happend

But that is okay

Array ( [type] => 1 [message] => Call to undefined function myBadTypo() [file] => /home/michael/www/eggplant/public_html/samples/fatal/FileThatBlowsMemLimits.php [line] => 3 )

Finally, let’s say we get the required file sorted out but blow the bank with available memory:

…we are able to catch the E_ERROR fatal error in our shutdown handler too:

Bad Stuff Happend

But that is okay

Array ( [type] => 1 [message] => Allowed memory size of 262144 bytes exhausted (tried to allocate 550001 bytes) [file] => /home/michael/www/eggplant/public_html/samples/fatal/FileThatBlowsMemLimits.php [line] => 9 )

That’s all pretty good, however there are some limits that need to be taken into consideration:

  • E_PARSE errors on the include that builds out the shutdown structure will not be caught, as the interpreter can’t get beyond the parse error to put it into place.
  • E_CORE_ERROR errors? These are environmental errors caught when PHP itself is bootstrapping – pre-script interpretation. No dice.
  • When using output buffering in combination with the ob_gzhandler, take care when cleaning the buffer. Chances are the Content-Encoding: gzip header was already set and you will need to follow suit.
  • If your output buffer has already been flushed before hitting a fatal error and the shutdown function, your content to that point is already gone. Check ob_get_status to see if that is the case.

For an similar implementation using a shutdown function, check out eZ Components’ Execution class.

Share Comments
comments powered by Disqus