Error Handling and Debugging in PHP

error

In this article I'm going to cover Error Handling and Debugging in PHP. If you get frustrated or confused when debugging your PHP scripts, this article is full of tips and techniques that will help you to fix your errors successfully.

Errors, errors errors! They are the worst nightmare of developers. They are everywhere and it usually takes you a long time to fix them just because you don't fully understand where they come from or why they occurred.

If you don't manage to fix your errors after a long while, this is the first and, perhaps, the most important piece of advice about debugging:

Be patient. Step away from your computer and take a brake. If you persist on continuing, things will get worse and worse.

Error Types in PHP

In PHP errors fall into one of the following categories:

  • Syntactical
  • Run-time
  • Logical

Syntactical errors are the easiest ones to make. Fortunately, they are also the easiest ones to find and fix. For example, if you omit a semicolon at the end of a statement, PHP will show you a parse error (if display_errors is off, you'll get a blank page) and prevent the script from executing. Let's look carefully at the following piece of code:

<?php

echo "Hello!"

echo "¡Hola!"

?>

If you run the previous script through a web server with PHP, you'll get the following error:

As you can read, this error is because you omitted the semicolon at the end of line 3 (echo "Hello!"). This is what it's called a syntactical error in PHP. As you can see in the previous image, PHP tells you not only the error type, but also the line where you may find the error. IDEs (Integrated Development Environments) normally are able to detect this kind of errors while you're coding your script. This is very useful because you can fix the error before running the script.

In order to avoid this kind of errors, remember to: end every statement with a semicolon; balance all quotation marks, parentheses, curly braces, and square brackets; close single quotes with single quotes and double quotes with double quotes; use the backslash to escape any single or double quotes within a string.

Run-time errors involve everything that doesn't prevent the script from running. In addition, they show a warning message with information about the error. If you, for example, call a function using a wrong number of parameters, PHP will show you a run-time error. The following piece of code:

<?php

function sumThreeNumbers($a, $b, $c) {

  return $a + $b + $c;

}

$a = 1;

$b = 2;

$c = 3;

$result = sumThreeNumbers($a, $b);

echo $result;

?>

will create the following run-time error:

Logical errors are due to conceptual errors in the creation of your script and they are the worst you can get. They don't prevent the script from executing nor do they report any error. If your script does not work as expected, it is highly likely that you failed to design it by making this type of mistakes.

There is no instruction manual to find and fix this kind of errors. It is a question of verifying, step by step and following the natural flow of the script, each line of code until you find the piece/s of code that caused the error/s.

Getting started

Once you already know the various types of errors in PHP, it's time to start debugging. Before you start with the hard part, you should take into account the following string of basic steps:

1.- If you are frustrated, you'd better go for a walk to clear your mind. After clearing your head, you will be able to think of a better solution for fixing your script.

2.- Make sure you are editing the file where PHP thinks you might fix the error. If you get confused with the right file to be edited, all your changes won't take effect and PHP, obviously, will continue showing you the same error.

3.- In this regard, be certain you saved the latest changes before uploading the file you just edited.

4.- Make sure what version of PHP is running on the web server. Likewise, know what web server is running your hosting company or your own computer (Apache, IIS, etc.). Use the phpinfo() function to get all the information you need to know about your PHP configuration.

5.- If your script doesn't work and you are sure there is nothing wrong with it, try executing it using another web server. Sometimes you get errors because of a certain version or configuration on your web server.

Displaying Errors and Adjusting Error Reporting

By default what you get when there are errors in your script is a blank page. This is suitable for live sites, but it makes no sense when it comes to debugging: a blank page wouldn't be useful for you to fix the script!

In order for PHP to display errors instead of a blank page, its display_errors directive has to be turned on. There are two ways to do it: either by using the ini_set() function in your scripts or by setting a value of 1 to this directive in the PHP configuration file (php.ini).

If you turn on the display_errors directive by using the ini_set() function, you will still get a blank page if there is an error that prevents your script from executing.

Let's look carefully at the following piece of code:

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Display Errors directive</title>
</head>

<body>
<h1>Testing the Display Errors directive</h1>
<?php
ini_set('display_errors', 1);

foreach($products as $p) {

  echo $p;

}
?>
</body>
</html>

After executing the previous piece of code, you will get the following run-time error:

As you can see in the previous image, the display_errors directive is working properly. Since the $products array was not declared before being supplied for the foreach loop, a run-time error is displayed.

In order to display any error in your script, the ini_set() function should be placed right after the opening PHP tag (<?php). Every PHP script starts with <?php and ends with ?>

In addition to setting how errors are displayed, PHP also allows you to set the default level of error reporting. As with display_errors, there are two ways to do it:

Either by using the error_reporting() function in your scripts or by setting the value for this directive in the PHP configuration file (php.ini).

The error_reporting() function takes either a number or a constant as its single and optional parameter. Click on the previous link to see all the available values in the PHP manual.

Let's look closely at the following piece of code:

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Error Reporting</title>
</head>

<body>
<h1>Testing Error Reporting</h1>
<?php
// Turn the display_errors directive on.
ini_set('display_errors', 1);

// Notices, Warnings, Errors and Recommendations will be reported.
error_reporting(E_ALL | E_STRICT);

foreach($products as $p) {

  echo $p;

}
?>
</body>
</html>

The previous piece of code will report the following errors:

As you can see in the previous image, now you get a new kind of error called Notice (thanks to the combination of E_ALL & E_STRICT), which reports an issue with the $products variable. Notices don't prevent the script from executing and may not necessarily be a problem.

This notice wasn't reported by the previous script because the default value used by PHP for error reporting is E_ALL & ∼E_NOTICE. This setting disables the reporting of notices.

PHP allows you to use bitwise operators to combine the previous values or mask out certain types of errors, but note that only '|', '∼', '!', and '&' will be understood within php.ini

More about bitwise operators in the PHP manual.

The @ symbol can be used in PHP for suppressing individual errors, but note that it only works on expressions like function calls or mathematical operations. It can't be used with conditionals, loops, function definitions... For example:

$a = 2;
$b = 0;
$result = @($a/$b);

$a = 1;

$b = 2;

$c = 3;

$result = @sumThreeNumbers($a, $b);

There are other PHP functions that are used to handle errors as well. You can use the die() and exit() functions (well, in reality they are constructors, not functions) to stop your script from continuing. In addition, the trigger_error() function can be used to generate an error, a warning or a notice message everywhere you wish in the script. Please refer to the PHP manual to go further on these functions.

Creating your own error-handling function

There is even another way to handle errors that allows you to not only customize how they are treated but also skip the ini_set() and error_reporting() functions you used above. This new way is by creating your own error-handling function. Let's look at the following example to understand how it works:

1.- Create a variable to store the status of your site. As error handling will vary depending on the status of your site (live or development), you have to create a variable to keep track of it.

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>My own error-handling function</title>
</head>

<body>
<h1>Testing my own error-handling functions</h1>
<?php
// Site status.
define('LIVE', FALSE);
?>
</body>
</html>

2.- Create your own error-handling function. This function can take up to five parameters: $e_number, $e_message, $e_file, $e_line, and $e_vars.

You can find an explanation of what each of these parameters mean in the PHP manual.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {
} // End of my_own_error_handler() definition.
?>

3.- Create the error message to be displayed. Start creating the error message to be displayed by using the received values. These values are passed to this function every time an error occurs.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

} // End of my_own_error_handler() definition.
?>

4.- Add any existing variable to the error message. When it comes to debugging, any useful information is welcome. $e_vars contains all the existing variables (and their values) when the error occurred. The print_r() function is for printing human-readable information about a variable, so use it with a value of 1 or TRUE as its second argument to return the content of the $e_vars variable.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

} // End of my_own_error_handler() definition.
?>

You can find more information on the print_r() function in the PHP manual.

5.- Print the error message. The error message will vary depending on whether or not the site is live.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

  // Print the appropriate error message.
  if (!LIVE) { // Development

    echo '<pre>' . $msg . "\n";

    debug_print_backtrace();

    echo '</pre><br />';

  } else { // Live, do not show my error message.

    echo '<div class="error"> There was an error. Please try later.</div><br />';

  }

} // End of my_own_error_handler() definition.
?>

If the site isn't live, then a detailed error message is printed with the help of the <pre> tag pair and the debug_print_backtrace() function. This function prints a PHP backtrace, that is, it prints the function calls, included/required files and so forth.

You can find more information on the debug_print_backtrace() function in the PHP manual.

If the site is live, you should print a simple error message for letting visitors know that there was an error with the site. Do not reveal any information about the nature of the problem when the site is live.

6.- Tell PHP to use your own error handler. After defining your own error handler, you have to tell PHP to use it.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

  // Print the appropriate error message.
  if (!LIVE) { // Development

    echo '<pre>' . $msg . "\n";

    debug_print_backtrace();

    echo '</pre><br />';

  } else { // Live, do not show my error message.

    echo '<div class="error"> There was an error. Please try later.</div><br />';

  }

  return true; // So that PHP does not try to handle the error too.

} // End of my_own_error_handler() definition.

// Use my own error handler.
set_error_handler('my_own_error_handler');
?>

7.- Create some errors. Once your own error handler is complete, create some errors to test how it works.

<?php
// Site status.
define('LIVE', FALSE);

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

  // Print the appropriate error message.
  if (!LIVE) { // Development

    echo '<pre>' . $msg . "\n";

    debug_print_backtrace();

    echo '</pre><br />';

  } else { // Live, do not show my error message.

    echo '<div class="error"> There was an error. Please try later.</div><br />';

  }

  return true; // So that PHP does not try to handle the error too.

} // End of my_own_error_handler() definition.

// Use my own error handler.
set_error_handler('my_own_error_handler');

foreach($products as $p) {

  echo $p;

}
?>

This is what you get after executing the previous piece of code:

As you can see in the previous image, the error-handling function returns an error message with little information. This is because there isn't much code in the script you just created. In a real-world script with more code, the message returned by the error handler would be much more useful for you.

If you change the value of the LIVE constant to TRUE, you'll get the following error messages:

As said above, it's important not to reveal any information about the nature of the problem when the site is live. Now, one simple message is printed in the web browser for each of the two errors in the script.

Taking your error-handling function further

There are a couple of things you can add to your error-handling function to make it even better. The first thing is not to report every single error (notices, warnings, and errors) when the site is live. For example, you can tell PHP to ignore any notice like this:

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

  // Print the appropriate error message.
  if (!LIVE) { // Development

    echo '<pre>' . $msg . "\n";

    debug_print_backtrace();

    echo '</pre><br />';

  } elseif ($e_number != E_NOTICE) { // Live, do not show my error message.

    echo '<div class="error"> There was an error. Please try later.</div><br />';

  }

  return true; // So that PHP does not try to handle the error too.

} // End of my_own_error_handler() definition.

The second thing you can do is to log the errors by using the error_log() function.

You can find more information on the error_log() function in the PHP manual.

Because you want to be told about anything that is not working well on your site, you are going to add a piece of code in order to send the error messages to your e-mail address.

// My own error handler.
function my_own_error_handler($e_number, $e_message, $e_file, $e_line, $e_vars) {

  // Error message to be displayed.
  $msg = "An error occurred in '$e_file' on line $e_line: $e_message\n";

  // Add any existing variable to the error message.
  $msg .= print_r($e_vars, 1);

  // Print the appropriate error message.
  if (!LIVE) { // Development

    echo '<pre>' . $msg . "\n";

    debug_print_backtrace();

    echo '</pre><br />';

  } elseif ($e_number != E_NOTICE) { // Live, do not show my error message.

    // Send the error in an email.
    error_log($message, 1, $contact_email, 'From:admin@mysite.com');

    echo '<div class="error"> There was an error. Please try later.</div><br />';

  }

  return true; // So that PHP does not try to handle the error too.

} // End of my_own_error_handler() definition.

The $contact_email variable is intended to store your e-mail address. The best place to declare it is at the beginning of the script.