This chapter is from the book

This chapter is from the book

Errors are a fact of life. Mr. Murphy has an entire collection of laws
detailing the prevalence and inescapability of errors. In programming, errors
come in two basic flavors:

External errorsThese are errors in which the code takes an
unanticipated path due to a part of the program not acting as anticipated. For
example, a database connection failing to be established when the code requires
it to be established successfully is an external error.

Code logic errorsThese errors, commonly referred to as
bugs, are errors in which the code design is fundamentally flawed due to
either faulty logic ("it just doesn't work that way") or
something as simple as a typo.

These two categories of errors differ significantly in several ways:

External errors will always occur, regardless of how "bug free"
code is. They are not bugs in and of themselves because they are external to the
program.

External errors that aren't accounted for in the code logic can be
bugs. For example, blindly assuming that a database connection will always
succeed is a bug because the application will almost certainly not respond
correctly in that case.

Code logic errors are much more difficult to track down than external
errors because by definition their location is not known. You can implement data
consistency checks to expose them, however.

PHP has built-in support for error handling, as well as a built-in severity
system that allows you to see only errors that are serious enough to concern
you. PHP has three severity levels of errors:

E_NOTICE

E_WARNING

E_ERROR

E_NOTICE errors are minor, nonfatal errors designed to help you
identify possible bugs in your code. In general, an E_NOTICE error is
something that works but may not do what you intended. An example might be using
a variable in a non-assignment expression before it has been assigned to, as in
this case:

<?php
$variable++;
?>

This example will increment $variable to 1 (because variables are
instantiated as 0/false/empty string), but it will generate an E_NOTICE error.
Instead you should use this:

<?php
$variable = 0;
$variable++;
?>

This check is designed to prevent errors due to typos in variable names. For
example, this code block will work fine:

<?
$variable = 0;
$variabel++;
?>

However, $variable will not be incremented, and $variabel will be. E_NOTICE
warnings help catch this sort of error; they are similar to running a Perl
program with use warnings and use strict or compiling a C program with
Wall.

In PHP, E_NOTICE errors are turned off by default because they can
produce rather large and repetitive logs. In my applications, I prefer to turn
on E_NOTICE warnings in development to assist in code cleanup and then
disable them on production machines.

E_WARNING errors are nonfatal runtime errors. They do not halt or
change the control flow of the script, but they indicate that something bad
happened. Many external errors generate E_WARNING errors. An example is
getting an error on a call to fopen() to mysql_connect().

E_ERROR errors are unrecoverable errors that halt the execution of
the running script. Examples include attempting to instantiate a non-existent
class and failing a type hint in a function. (Ironically, passing the incorrect
number of arguments to a function is only an E_WARNING error.)

PHP supplies the trigger_error() function, which allows a user to
generate his or her own errors inside a script. There are three types of errors
that can be triggered by the user, and they have identical semantics to the
errors just discussed:

In addition to these errors, there are five other categories that are
encountered somewhat less frequently:

E_PARSEThe script has a syntactic error and could
not be parsed. This is a fatal error.

E_COMPILE_ERRORA fatal error occurred in the engine
while compiling the script.

E_COMPILE_WARNINGA nonfatal error occurred in the
engine while parsing the script.

E_CORE_ERRORA fatal runtime error occurred in the
engine.

E_CORE_WARNINGA nonfatal runtime error occurred in
the engine.

In addition, PHP uses the E_ALL error category for all error
reporting levels.

You can control the level of errors that are percolated up to your script by
using the php.ini setting error_reporting.
error_reporting is a bit-field test set that uses defined constants,
such as the following for all errors:

error_reporting = E_ALL

error_reporting uses the following for all errors except for E_NOTICE, which
can be set by XOR'ing E_ALL and E_NOTICE:

error_reporting = E_ALL ~ E_NOTICE

Similarly, error_reporting uses the following for only fatal errors (bitwise
OR of the two error types):

error_reporting = E_ERROR | E_USER_ERROR

Note that removing E_ERROR from the error_reporting level does not allow you
to ignore fatal errors; it only prevents an error handler from being called for
it.

Handling Errors

Now that you've seen what sort of errors PHP will generate, you need to
develop a plan for dealing with them when they happen. PHP provides four choices
for handling errors that fall within the error_reporting threshold:

Display them.

Log them.

Ignore them.

Act on them.

None of these options supersedes the others in importance or functionality;
each has an important place in a robust error-handling system. Displaying errors
is extremely beneficial in a development environment, and logging them is
usually more appropriate in a production environment. Some errors can be safely
ignored, and others demand reaction. The exact mix of error-handling techniques
you employ depends on your personal needs.

Displaying Errors

When you opt to display errors, an error is sent to the standard output
stream, which in the case of a Web page means that it is sent to the browser.
You toggle this setting on and off via this php.ini setting:

display_errors = On

display errors is very helpful for development because it enables you to get
instant feedback on what went wrong with a script without having to tail a
logfile or do anything but simply visit the Web page you are building.

What's good for a developer to see, however, is often bad for an end
user to see. Displaying PHP errors to an end user is usually undesirable for
three reasons:

It looks ugly.

It conveys a sense that the site is buggy.

It can disclose details of the script internals that a user might be able
to use for nefarious purposes.

The third point cannot be emphasized enough. If you are looking to have
security holes in your code found and exploited, there is no faster way than to
run in production with display_errors on. I once saw a single incident
where a bad INI file got pushed out for a couple errors on a particularly
high-traffic site. As soon as it was noticed, the corrected file was copied out
to the Web servers, and we all figured the damage was mainly to our pride. A
year and a half later, we tracked down and caught a cracker who had been
maliciously defacing other members' pages. In return for our not trying to
prosecute him, he agreed to disclose all the vulnerabilities he had found. In
addition to the standard bag of JavaScript exploits (it was a site that allowed
for a lot of user-developed content), there were a couple particularly clever
application hacks that he had developed from perusing the code that had appeared
on the Web for mere hours the year before.

We were lucky in that case: The main exploits he had were on unvalidated user
input and nondefaulted variables (this was in the days before
register_global). All our database connection information was held in
libraries and not on the pages. Many a site has been seriously violated due to a
chain of security holes like these:

Leaving display_errors on.

Putting database connection details (mysql_connect()) in the
pages.

Allowing nonlocal connections to MySQL.

These three mistakes together put your database at the mercy of anyone who
sees an error page on your site. You would (hopefully) be shocked at how often
this occurs.

I like to leave display_errors on during development, but I never
turn it on in production.

Production Display of Errors

How to notify users of errors is often a political issue. All the large
clients I have worked for have had strict rules regarding what to do when a user
incurs an error. Business rules have ranged from display of a customized or
themed error page to complex logic regarding display of some sort of cached
version of the content they were looking for. From a business perspective, this
makes complete sense: Your Web presence is your link to your customers, and any
bugs in it can color their perceptions of your whole business.

Regardless of the exact content that needs to be returned to a user in case
of an unexpected error, the last thing I usually want to show them is a mess of
debugging information. Depending on the amount of information in your error
messages, that could be a considerable disclosure of information.

One of the most common techniques is to return a 500 error code from the page
and set a custom error handler to take the user to a custom error page. A 500
error code in HTTP signifies an internal server error. To return one from PHP,
you can send this:

header("HTTP/1.0 500 Internal Server Error");

Then in your Apache configuration you can set this:

ErrorDocument 500 /custom-error.php

This will cause any page returning a status code of 500 to be redirected
(internallymeaning transparently to the user) to /custom-error.php.

In the section "Installing a Top-Level Exception Handler," later in
this chapter, you will see an alternative, exception-based method for handling
this.

Logging Errors

PHP internally supports both logging to a file and logging via
syslog via two settings in the php.ini file. This setting sets
errors to be logged:

log_errors = On

And these two settings set logging to go to a file or to syslog,
respectively:

error_log = /path/to/filename
error_log = syslog

Logging provides an auditable trace of any errors that transpire on your
site. When diagnosing a problem, I often place debugging lines around the area
in question.

In addition to the errors logged from system errors or via
trigger_error(), you can manually generate an error log message with
this:

error_log("This is a user defined error");

Alternatively, you can send an email message or manually specify the file.
See the PHP manual for details. error_log logs the passed message, regardless of
the error_reporting level that is set; error_log and error_reporting are two
completely different entries to the error logging facilities.

If you have only a single server, you should log directly to a file.
syslog logging is quite slow, and if any amount of logging is generated
on every script execution (which is probably a bad idea in any case), the
logging overhead can be quite noticeable.

If you are running multiple servers, though, syslog's
centralized logging abilities provide a convenient way to consolidate logs in
real-time from multiple machines in a single location for analysis and archival.
You should avoid excessive logging if you plan on using syslog.

Ignoring Errors

PHP allows you to selectively suppress error reporting when you think it
might occur with the @ syntax. Thus, if you want to open a file that
may not exist and suppress any errors that arise, you can use this:

$fp = @fopen($file, $mode);

Because (as we will discuss in just a minute) PHP's error facilities do
not provide any flow control capabilities, you might want to simply suppress
errors that you know will occur but don't care about.

Consider a function that gets the contents of a file that might not
exist:

$content = file_get_content($sometimes_valid);

If the file does not exist, you get an E_WARNING error. If you know that this
is an expected possible outcome, you should suppress this warning; because it
was expected, it's not really an error. You do this by using the @
operator, which suppresses warnings on individual calls:

$content = @file_get_content($sometimes_valid);

In addition, if you set the php.ini setting track_errors = On, the last error
message encountered will be stored in $php_errormsg. This is true regardless of
whether you have used the @ syntax for error suppression.

Acting On Errors

PHP allows for the setting of custom error handlers via the
set_error_handler() function. To set a custom error handler, you define
a function like this:

Now when an error is detected, instead of being displayed or printed to the
error log, it will be inserted into a database table of errors and, if it is a
fatal error, a message will be printed to the screen. Keep in mind that error
handlers provide no flow control. In the case of a nonfatal error, when
processing is complete, the script is resumed at the point where the error
occurred; in the case of a fatal error, the script exits after the handler is
done.

Mailing Oneself

It might seem like a good idea to set up a custom error handler that uses the
mail() function to send an email to a developer or a systems
administrator whenever an error occurs. In general, this is a very bad idea.

Errors have a way of clumping up together. It would be great if you could
guarantee that the error would only be triggered at most once per hour (or any
specified time period), but what happens more often is that when an unexpected
error occurs due to a coding bug, many requests are affected by it. This means
that your nifty mailing error_handler() function might send 20,000
mails to your account before you are able to get in and turn it off. Not a good
thing.

If you need this sort of reactive functionality in your error-handling
system, I recommend writing a script that parses your error logs and applies
intelligent limiting to the number of mails it sends.