Error reporting basics

  1. TL;DR
  2. The Rules
  3. Bad, bad examples
  4. Just leave them alone
  5. What if some code refuses to raise an error?
  6. Showing a nice page for a user
  7. Comments (1)

Due to various historical reasons, almost every newbie PHP user is taking the error reporting wrong. There are thousands of malpractices, wrong decisions and even superstitions. As a result, the error reporting becomes inflexible, unmaintainable, not user-friendly and even harmful.

Yet, the proper way is ridiculously simple

TL;DR

Never write a single operator that outputs an error message directly to the screen. Instead, make PHP code to generate errors by itself, and then direct these messages appropriately: on a dev server they have to be shown on-screen; whereas on a live server they should be logged, while only a generalized error page without particular details should be shown to a site visitor.

To do so, configure your PHP as follows:

Note that these values preferred to be set in php.ini or in the the server's configuration:

Only as a last resort set them right in the script using ini_set() command:

error_reporting(E_ALL);
ini_set('display_errors'1);

but it will fail for some types of errors (parse errors for example)

And then create an error handler for a live site that is showing an excuse page in case of PHP error.

The Rules

To make error reporting the right way, one should always follow two cornerstone rules:

On a developer's PC the error message should be shown in the full detail, along with other useful information, to ease the development process.

On a live site, not a single word from the error message should be shown to a site user. Because:

Instead, the error message should be logged for the future reference, whereas just a generic error page should be shown.

Bad, bad examples

And this separation - between a live and a dev server - is the very source of confusion. As a matter of fact, many newbie PHP users are writing their code as though they will always be the only users of their sites, bluntly directing error messages right on the screen. Don't you trust me? How many times did you see such a code:

$result mysqli_query($conn$query) or die(mysqli_error($conn));

or

try {
    
$stmt $pdo->query($sql);
} catch (
PDOException $e) {
    die(
$e->getMessage());
}

?

Many php tutorials, including - sadly - the PHP manual, are using this terrible approach. Given the rules above, you can tell why it's wrong: it is intended for the development only, but absolutely unsuitable for a live site.

So how to arrange the error handling to fulfill such contradicting guidelines: showing errors in the full force for a developer yet hide them completely from a site visitor?

Just leave them alone

The answer is surprisingly simple: just leave error messages alone

Although it sounds alien to many PHP users (who cannot imagine a single database interaction without several lines of diligently written error handling code), yet this is the very truth: by leaving the error message alone, one will make its handling flexible and easily configurable, making it possible to switch between modes with just a single PHP configuration option!

Just think of it: there is a php.ini directive called display_errors. By setting it to 1 we will switch our site into development mode, as PHP will start showing every error occurred. An by setting it to 0, we will mute error messages completely, at least barring a hacker from the critical information and making a user less confused. Still, we need to show a page with excuses, but that's a little different story which we will discuss later.

Think of it this way: an uncaught Exception makes a regular PHP error, so it makes no sense to write a special code only to show it: PHP can show you errors already! So just leave it alone and it will behave exactly as any other error, according to the site-wide rules.

What if some code refuses to raise an error?

There are some PHP modules which, by default, silently fail instead of raising an error. Well, the answer is simple: most of these modules has a secret configuration option that will make them behave as good boys - raise errors just by themselves. If some PHP function doesn't raise an error but apparently fails, then search for the option to turn the error reporting for such a module on. For example, PDO should be always configured to throw exceptions, as well as mysqli.

But sometimes there is still no way to make a function to report its error by itself. A good example is the late mysql_query() function or json_decode(): both will silently return false in case of error. In this situation you should look around once more and look for the error message provider dedicated to this function. In our case it will be mysql_error() and json_last_error_msg() respectively.

But again, this function's output should never ever be fed to the notorious die() operator!

Instead, it should be transferred into a PHP error. The simplest way to do so is to use the trigger_error() function:

$result mysql_query($query) or trigger_error(mysql_error());

in case you have to deal with legacy code that is ultimately using this outdated extension that has been completely removed from the language in 2015, at least the error reporting should be done this way.

But a much better way would be to throw an exception. The simplest way is just to test the function's result and to add a throw operator (note that you cannot use it neatly with or, the condition should be explicit):

if ($error json_last_error_msg())
{
    throw new \
Exception("JSON decode error: $error");
}

But that's just a quick and dirty solution. The best way would be to create a dedicated Exception class for such an error. It's not a big deal, basically it's just a single line:

class JsonDecodeException extends ErrorException {}

yet after defining the dedicated class we can create a more talkative version of json_decode():

function jsonDecode($json$assoc false)
{
    
$ret json_decode($json$assoc);
    if (
$error json_last_error())
    {
        throw new 
JsonDecodeException(json_last_error_msg(), $error);
    }
    return 
$ret;
}

Now it will start spitting the error telling us the reason, just like expected:

Fatal errorUncaught exception 'JsonDecodeException' with message 'Syntax error' in /home/me/test.php:9
Stack trace
:
#0 /home/me/test.php(14): jsonDecode('foobar')
#1 {main}
  
thrown in /home/me/test.php on line 9

This message will be sent either to the screen or the error log, telling us that there was an error with decoding JSON, caused by the wrong JSON syntax.

Showing a nice page for a user

All right, it's OK with a programmer, they are notified of the every error occurred, either from a log file or just by watching the screen. But what about a site user? With proper error reporting set, they will just face a blank page, which is apparently not the way to go. We need to show them some explanations and ask to try later. There are several ways to do that. The simplest way is to configure your web-server to show such a page in case of error. For example, to configure Nginx to show a custom error page in case of 500 error, just create such a custom error page, and then add this line to the server configuration:

error_page 500 502 503 504 /500.html

Voila! In case of error this page will be shown instead of a blank screen, making your users happy! Do not forget to create the actual 500.html page and put it in the site root.

However, it is reported that Apache's mod_php has issues with its ErrorDocument directive and a 500 error generated by PHP. To make the solution robust and have more control on the error handling it's better to handle error on the PHP side, but still do it centralized way. There is a thing in PHP called error handler. Just create a function that is doing everything you need in case of error and then register it as an error handler. Here goes a little example:

set_error_handler("myErrorHandler");
function 
myErrorHandler($errno$errstr$errfile$errline)
{
    
error_log("$errstr in $errfile:$errline");
    
header('HTTP/1.1 500 Internal Server Error'TRUE500);
    
readfile("500.html");
    exit;
}

This function doesn't do very much but at least it is useful for a live site, as it follows all the guidelines - it logs the error message, then shows the generic error page for the user and then terminates the execution.


Related articles: