drilix.com

Freedom, Community & Sustainability

Debugging PHP errors

October 27, 2014 -- William
Last modified on September 2016
Duration: +- 30 minutes

BugDebugging is probably the most important skill of a developer. It is often and underrated task because it doesn't create anything, it just makes things work as they should. On top of it, debugging is most of the time a tedious or stressful task (depending on what the error breaks for your users!). As a programer you should learn how to debug efficiently to be able to do it quickly and keep going with your life.

View the bug messages

Especially when you get a white page of death (WPOD) you won't have any clue of what's going on unless you have access to error messages. There are two important configurations that will help you in that direction: error_reporting, sets the level of errors that should be reported and display_errors, sets if errors should be displayed or not.

You can set these configurations in different places, in order of hierarchical precedence (meaning that the latter will override the former): PHP configuration (php.ini), server configuration (apache.conf, httpd.conf or .htaccess files) and on the PHP script itself. You should be aware that each configuration has a different effect, for example, php.ini and apache changes affect the entire server and is the preferred method on a development environment. Configurations in the script itself will only affect the given script and take precedence over server configurations but will not work if there is a parse error on your file and the script is not executed.

To set these configurations in PHP you should locate and edit the php.ini file, usually located in /etc/php5/apache2/php.ini (use the command "locate php.ini" to find it in your machine). Search and edit the file so that the following lines match the configurations:


error_reporting = E_ALL | E_STRICT
display_errors = On
display_startup_errors = On

Then you need to restart the server with the command "sudo service apache2 restart". It's roughly the same process to set these configurations on your server.

To set the configurations on your PHP script, just add the following lines at the beginning of your source code:


error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);

Alternatively you can chose to log the errors in a file instead of displaying it on the browser. This is the recommended way of setting a production environment. Usually the PHP errors are sent to the server log but it's a good idea to separate server logs from PHP logs. To set a specific log file for PHP set the configurations as follows (either in php.ini or the script as shown above):


log_errors = On
log_errors_max_len = 0
error_log = /path/to/file/php_errors.log

To view the live log file you can use the command "tail -f /path/to/file/php_errors.log" (create the file manually, it won't work if the file does not exist!). Press Ctrl+C to exit the log. Do not save log files inside the web root since they would be accessible from the Internet and give away critical information to malicious users.

Understand the messages

There are three severity levels in (order of gravity) notice, warning and error. Errors will prevent the script from being executed, also called "fatal error", often causing a white screen of death and they are the worst (and most urgent) kind of problems. Warnings are intended to state that the script is executed but maybe not as expected. Notices are a hint that things are ok but could be improved.

These bugs can occur at two different times, when the program is being interpreted (compile-time), in that case the script won't execute at all, and when the program is running (run-time), causing the error to be executed. Usually compile-time errors are related to parse errors, meaning that your code breaks the rules of the programming language and cannot be interpreted. Run-time errors means that the syntax of your code is ok but the program is doing something wrong.

The error levels are named by a constant. The most common errors you will encounter are E_ERROR (fatal run-time error), E_WARNING (run-time warning), E_PARSE (compile-time error), E_NOTICE (run-time notice), E_STRICT (compile-time notice, usually encouraging best practises), E_DEPRECATED (run-time notice, usually when old code is being used) and E_ALL (all errors, warnings and notices).

Together with the error level you will also see a message specific to the given bug. These message are usually quite explicit, telling you what went wrong, in what file and line it happens. Open the file and go to the specified line to understand what is wrong in your code.

Triggering and handling errors explicitly

When debugging you can insert errors on purpose to better understand what part of the code is executed. In it's simplest form, just write exit; or die; in any part of your code to know if this portion of the code is executed. Unfortunately, if the code is executed the program will stop! It's certainly helpful as a quick diagnose but doesn't give us a lot of information about the rest of the code. It's better if you put some more information like adding a message yourself or passing a variable to the die function: die("The file $filename was not found");

A quick way of getting more information from the chain of events that led to the specific code is to call the function debug_backtrace.  Backtraces are useful when dealing with functions/methods that are nested into one another. The backtrace will show you the sequence of function calls from the point where it's called up to the first function that calls it. Before your exit; line you can enter var_dump(debug_backtrace()); to get the sequence of events that led to the given portion of code.

A better way to deal with errors that you can foresee during development is to trigger the error gracefully and create custom error messages for your specific code. Triggering errors gracefully is a way to help your users and yourself when you are writing code and you know it can potentially go wrong. Usually you will put these in an if statement that will trigger the error only if a certain condition is met. These custom generated errors should be marked as "USER" errors. The error levels are E_USER_NOTICE, E_USER_WARNING and E_USER_ERROR. To trigger the error you will use the function trigger_error('Message', ERROR_LEVEL); as such:


if (!file_exists($file)) {
   trigger_error('The file does not exist', E_USER_ERROR);
}

Additionally, you can handle the errors to have even more control. The above error E_USER_ERROR will cause the program to stop execution. The good news is that there is a way to get some output even when the program crashes for either normal or triggered errors. The function you will use is register_shutdown_function. As the function name states, it is executed just before a crash and works with an associated callback function. In this case the callback function will fetch information from error_get_last, that is the last error before crash.


register_shutdown_function('bug_handler');
function bug_handler() {
    $bug = error_get_last();
    if(!empty($bug)) {
        var_dump($bug);
    }
}

In other situations you might want to just call a handler function even if the error is not fatal in order to take better care of the problem. To do this you will use the function set_error_handler(). It works pretty much like the first one but you will have to pass the variables from the error to your callback function and exit/continue the program accordingly. Note that if I get a E_USER_ERROR my callback function will terminate the program with the exit() function.


set_error_handler('bug_handler');
function bug_handler($type, $message, $file, $line) {
    $bug = "$type - $message - from file $file on line $line\n";
    switch ($type) {
        case E_USER_ERROR:
            echo "<b> PROGRAM CRASHED! : $bug </b>";
            exit;
        default:
            echo "<b> WATCH OUT! : $bug </b>";
            break;
    }
}
           

Exceptions in Object Oriented PHP (throw, try and catch)

In OOP, errors are called exceptions. An exception is an object from the class Exception and includes the following methods: getMessage(); getCode(); getFile(); getLine(); getTrace() and getTraceAsString(). They are pretty self-explanatory and you will understand them when you see them in the code.

When you want to test a series of operations, put them inside a try block. When an error occurs we have to throw an exception. Trowing the exception stops the execution of the try block and sends it to a catch block. The catch block will do what needs to be done in this situation. In it's simplest usage you can just put a certain code that you want to test into a try block and then catch it. Note: don't throw without catching!


try {
// the existing code you want to test is here
}
catch(Exception, $bug) {
   echo $bug->getMessage;
}

To do this using our previous example we would write the entire error handling process like this:


try {
    if (!file_exists($file)) {
        throw new Exception('The file does not exist');
    }
}
catch (Exception $bug ) {
    echo $bug->getFile() . ' ' . $bug->getMessage();
}

You can also make things even more granular by extending the Exception class and creating your own exception objects.


class FileNotFound extends Exception{}
try {
    if (!file_exists($file)) {
        throw new FileNotFound('The file does not exist');
    }
}
catch (FileNotFound $bug ) {
    var_dump($bug->getTrace());
    exit;
}

In this way you can establish the correct action to be taken for each separate exception.

Get e-mail notifications when problems occur

This is probably something you only want to do on very specific cases. Since this is likely to be used on production environments keep in mind that it can easily bloat your inbox. The only difference here is that we are replacing var_dump with the built in mailer function of PHP.


$to = "me@example.com";
$subject = "[{$_SERVER['SERVER_NAME']}] : An error occured";
$message = var_export($bug, TRUE) . PHP_EOL;
mail($to, $subject, $message);

On the next page we will see the additional debugging tools FirePHP and Xdebug.

Pages