ÄúµÄλÖãºÑ°ÃÎÍøÊ×Ò³£¾±à³ÌÀÖÔ°£¾PHP ±à³Ì£¾PHP 5 Power programming
Team LiB
Previous Section Next Section

7.2. Types of Errors

7.2.1. Programming Errors

Sometimes errors occur due to errors in our code. In some ways, these are the easiest errors to deal with because they can be uncovered mostly by straightforward testing, simply by trying out all the operations your application provides. Handling them is just a matter of correcting the code.

7.2.1.1 Syntax/Parse Errors

Syntax errors and other parse errors are caught when a file is compiled, before PHP starts executing it at all

<?php

    print "Hello!\n";
    <gobbledigook/>

    ?>

This example contains an XML tag where PHP expects to find code. Running this results in an error:

Parse error: parse error in test.php on line 4

As you can see, the script did not even print Hello! before displaying an error message, because the syntax error was discovered during compilation, before PHP started executing the script.

7.2.1.2 Eval

All syntax or parse errors are caught during compilation, except errors in code executed with eval(). In the case of eval, the code is compiled during the execution of the script. Here, we modify the previous example with eval:

<?php

    print "Hello!\n";
    eval("<gobbledigook/>");

    ?>

This time, the output is different:
Hello!

    Parse error: parse error in /home/ssb/test.php(4) : eval()'d
    code on line 1

As you can see, this time the error was displayed during execution. This is because code executed with eval() is not compiled until the eval() itself is executed.

7.2.1.3 Include / Require

If your script includes another file that has a parse error, compilation will stop at the parse error. Code and declarations preceding the parse error are compiled, and those following the error are discarded. This means that you will get a half-compiled file if there is a parse error in it.

The following example uses two files, error.php and test.php:

<?php
function foo() {
print "foo\n";
}
R$* < $+ :; > $*    $@ $2 :; <@>
function bar() {
print "bar\n";
}
?>
error2.php

(The line in the middle is not line noise; it is taken from the configuration file of sendmail, a UNIX mail server infamous for its unreadable configuration file format.)

<?php

require "error2.php";
print "Hello!\n";
foo();
bar();

?>
error3.php

the output from executing error3.php.

Figure 7.2. Output from executing error3.php.


What happens here? First, PHP compiles test.php and starts executing it. When it encounters the require statement, it starts compiling error.php, but aborts after the parse error on line 7 of error.php. However, the foo() function has already been defined because it was reached before the parse error. But, PHP never got around to defining the bar() function due to the parse error.

Next, in execution of test.php, PHP prints Hello!, calls the foo() function that prints foo, but fails trying to call bar() because it has not been defined.

7.2.2. Undefined Symbols

When PHP executes, it may encounter names of variables, functions, and so on that it does not know. Because PHP is a loosely typed interpreted language, it does not have complete knowledge about all symbol names, function names, and so on during compilation. This means that it may run into unknown symbols during execution. Although syntax errors are caught before the code is executed, errors regarding undefined symbols occur while the code runs.

7.2.2.1 Variables and Constants

Variables and constants are not dramatic, and they go by with just a notice (see the section about PHP error levels later in this chapter):

<?php
var_dump($undefined_variable);
var_dump(UNDEFINED_CONSTANT);
print "Still alive\n";

?>

The output is
Notice: Undefined variable:  undefined_variable in test.php on line 3
NULL

Notice: Use of undefined constant UNDEFINED_CONSTANT - assumed
'UNDEFINED_CONSTANT' in test.php on line 4
string(18) "UNDEFINED_CONSTANT"
Still alive!

As you can see, the undefined variable evaluates to NULL, while the undefined constant evaluates to a string with the name of the constant. The error messages displayed are just notices, which is the least significant type of PHP error messages.

Using undefined variables in PHP is not an error, just sloppy coding practice. Read the section on register_global security XXX ADDREF for some examples of what this could lead to in the worst-case scenario.

Technically, using undefined variables is okay, and if you disable notices it will not produce any error messages. However, because notices come in handy for other things (such as noticing undefined constants!), we recommend that you keep reporting them enabled and fix your undefined variables. As a last resort, you can silence the expressions that cause notices individually with the @ statement.

Undefined constants are bugs. A side effect of using an undefined constant is that it returns a string with the name of the constant, but never rely on this. Put your strings in quotes.

7.2.2.2 Array Indexes

Consider this example:

<?php

if ($_GET["name"]) {
print "Hello, $_GET[name]!<br>\n";
}

?>

If the page serving this script is requested without any GET parameters, it displays a notice:

test.php(3) : Notice - Undefined index:  name

7.2.2.3 Functions and Classes

Although PHP keeps executing after running across an undefined variable or constant, it aborts whenever it encounters an undefined function or class:

<?php

print "Yoda says:\n";
undefined_this_function_is();
print "Do or do not, there is no try.\n";

?>

The output is
Yoda says:

Fatal error: Call to undefined function: undefined_this_function_is()
     in test.php on line 4

The second print on line 5 was never executed because PHP exits with a fatal error when it tries to call the undefined function.

The same thing happens with an undefined class:

<?php

print "Yoda says:\n";
new undefined_class;
print "Do or do not, there is no try.\n";

?>

The output is
Yoda says:

Fatal error: Class 'undefined_class' not found in test.php on line 4

Classes have one exception. If there is a user-defined function called __autoload, it is called when PHP runs across an undefined class. If the class is defined after __autoload returns, the newly loaded class is used, and no fatal error occurs.

7.2.2.4 Logical Errors

Discovering parse errors or undefined symbols is relatively easy. A more subtle type of programming error is a logical error, errors that are in the structure and logic of the code rather than just the syntax.

The best ways to find logical errors is testing combined with code reviews.

7.2.3. Portability Errors

7.2.3.1 Operating System Differences

Although PHP itself runs on many different platforms, that does not automatically make all PHP code 100 percent platform-independent. There are always some OS-specific issues to consider. Here are some examples:

  • PHP functions that are available only on a specific platform

  • PHP functions that are not available on a specific platform

  • PHP functions that differ slightly on different platforms

  • Which character is used to separate path components in file names

  • External programs or services that are not available on all platforms

7.2.3.2 PHP Configuration Differences

With all the different options available in PHP's configuration file (php.ini), it is easy to get into trouble when making assumptions about these settings.

One common example is the magic_quotes_gpc ini option. If this option is enabled, PHP adds slashes (like the addslashes() function) on all external data. If you write your code on a system with this option disabled, and then move it to a server with magic_quotes_gpc enabled, your user input will suffer from "backslash pollution."

The correct way to handle such variations is to check your PHP code and see whether an option is enabled with the ini_get() function, and make the appropriate adjustments.

For example, in the magic_quotes_gpc case, you should do this:

<?php
$dbh = DB::connect("mysql://user:pw@localhost/test");
if (ini_get("magic_quotes_gpc")) {
stripslashes($_GET["email"]);
}
$dbh->query("INSERT INTO emails VALUES(?)", array($_GET["email"]));

?>

register_globals

The register_globals setting determines whether PHP should import GET, POST, cookie, environment, or server variables as global variables. In re-usable code, avoid relying on register_globals; instead, use the superglobal variables provided for accessing them ($_GET and friends).

register_argc_argv

This variable controls whether the global variables $argc and $argv should be set. In the CLI version of PHP, these are set by default and required for PHP to access command-line parameters.

magic_quotes_gpc, magic_quotes_runtime

Magic quotes is the name of a PHP feature that automatically quotes input data, by using the addslashes() function. Historically, this was used so that form data could be used directly in SQL queries without any security or quoting issues. Today, form data is used for much more, and magic quotes quickly get in the way. We recommend that you disable this feature, but portable code must be aware of these settings and deal with them appropriately by calling stripslashes() on GPS (GET, POST, and cookie) data.

y2k_compliance

The y2k_compliance set to on causes PHP to display four-digit years instead of two-digit years. Oddly enough, the only value that is known to cause problems with some browsers is on, which is why it is off by default.

unserialize_callback_func

This setting is a string with the name of the function used for de-serializing data when the unserialize() function is used.

arg_separator.input

When receiving GET and POST form data, the ampersand character (&) is used by default to separate key-value pairs. With this option, the separator character can be changed to something else, which could cause portability problems.

allow_url_fopen

By default, PHP's file functions support reading and writing URLs. If this option is set to false, URL file operations are disabled. You may need to deal with this in portable code, either by having a userland implementation in reserve, or by checking whether this option is set upon startup and refuse to run if URL file operations are not allowed.

7.2.3.3 SAPI Differences

PHP is not only available for many different operating systems, but it also offers native interfaces to a range of different Server APIs, or SAPIs in PHP lingo. The most common PHP SAPI is the Apache 1.3 module; others are CGI, CLI, the IIS filter, the embeddable version of PHP, and so on.

Some SAPIs offer PHP functions that are available only in that SAPI. For example, the Apache 1.3 SAPI offers a function called apache_note() to pass information to other Apache modules.

Table 7.1 shows some SAPI-specific functions.

Table 7.1. SAPI-Specific Functions

Function

SAPI Layers that Define It

ApacheRequest (class)

apache_hooks

apache_lookup_uri

apache, apache_hooks, apache2filter

apache_request_headers

apache, apache_hooks, apache2filter

apache_response_headers

apache, apache_hooks, apache2filter

apache_note

apache, apache_hooks, apache2filter

apache_setenv

apache, apache_hooks, apache2filter

apache_getenv

apache, apache_hooks

apachelog

apache, apache_hooks

apache_child_terminate

apache, apache_hooks

apache_exec_uri

apache, apache_hooks

getallheaders

aolserver, apache, apache_hooks, apache2filter

smfi_setflags

milter

smfi_settimeout

milter

smfi_getsymval

milter

smfi_setreply

milter

smfi_addheader

Milter

smfi_chgheader

milter

smfi_addrcpt

milter

smfi_delrcpt

milter

smfi_replacebody

milter

virtual

apache, apache_hooks, apache2filter


7.2.3.4 Dealing with Portability

Portability errors can be tricky to find because they require that you test your code thoroughly in different configurations on different systems. However, proper testing and code reviews are the best ways to find portability problems.

Of course, if you write and deploy all of your code on the same platform with a homogenous configuration, you may never run into any portability problems. Awareness of portability issues is a good thing anyway; it enables you to write better, more re-useable, and more robust code.

Fixing portability errors may be easy, such as checking the ini setting, as in the previous magic_quotes_gpc example. But it may be more difficult as well. You may need to parse the output of a command differently for different operating systems, or provide a fallback implementation written in PHP for something available only on some platforms.

In some cases, what you do is not even possible to do in a portable way.

In general, the best approach to portability problems is hiding the operating system or SAPI details in a code layer, abstracting away the problem. One example of such an abstraction is the System class from PEAR, which provides PHP implementations of some common UNIX commands and other common operations that are OS-specific.

7.2.3.5 Portability Tools

PEAR class: System

The System PEAR class is available as part of the basic PEAR install:

<?php

require_once "System.php";

$tmp_file = System::mktemp();
copy("http://php.net/robots.txt", $tmp_file);
$pear_command = System::which("pear");

?>

PEAR class: OS_Guess

The OS_Guess class uses the php_uname() function to determine on which operating system it is running. It also provides ways of generalizing and comparing OS signatures:

<?php

require_once "OS/Guess.php";

$os = new OS_Guess;
print "OS signature: " . $os->getSignature() . "\n";
if ($os->matchSignature("linux-*-i386")) {
print "Linux running on an Intel x86 CPU\n";
}

?>

Example output:
OS signature: linux-2.4-i386-glibc2.1
Linux running on an Intel x86 CPU

7.2.4. Runtime Errors

Once code is up and running, non-fatal runtime errors are the most common type of error in PHP. Runtime refers to errors that occur during execution of the code, which are not usually programming errors but caused factors outside PHP itself, such as disk or network operations or database calls.

PHP has an error-reporting mechanism that is used for all errors triggered inside PHP itself, either during compilation of the script or when executing a built-in function. You can use this error-reporting mechanism from a script as well, although there are more powerful ways of reporting errors (such as exceptions).

The rest of this chapter focuses on some forms of runtime errors. Even perfectly good code may produce runtime errors, so everyone has to deal with them in one way or another.

Examples of runtime errors occur when fopen() fails because a file is missing, when mysql_connect() fails because you specified the wrong username, if fsockopen() fails because your system runs out of file descriptors, or if you tried inserting a row into a table without providing a required not-null column.

7.2.5. PHP Errors

The error mechanism in PHP is used by all built-in PHP functions. By default, this simple mechanism prints an error message with file and line number and exits. In the previous section, we saw several examples of PHP errors.

7.2.5.1 Error Levels

PHP errors are categorized by an error level ranging from notices to fatal errors. The error level tells you how serious the error is. Most errors may be caught with a custom error handler, but some are unrecoverable.

E_ERROR

This is a fatal, unrecoverable error. Examples are out-of-memory errors, uncaught exceptions, or class redeclarations.

E_WARNING

This is the most common type of error. It normally signals that something you tried doing went wrong. Typical examples are missing function parameters, a database you could not connect to, or division by zero.

E_PARSE

Parse errors occur during compilation, and force PHP to abort before execution. This means that if a file fails with a parse error, none of it will be executed.

E_STRICT

This error level is the only one not included in the E_ALL constant. The reason for this is to make transition from PHP 4 to PHP 5 easier; you can still run PHP 4 code in PHP 5.

E_NOTICE

Notices are PHP's way to tell you that the code it runs may be doing something unintentional, such as reading that undefined variable. It is good practice to develop with notices enabled so that your code is "notice-safe" before pushing it live. On your production site, you should completely disable HTML errors.

E_CORE_ERROR

This internal PHP error is caused by an extension that failed starting up, and it causes PHP to abort.

E_COMPILE_ERROR

Compile errors occur during compilation, and are a variation of E_PARSE. This error causes PHP to abort.

E_COMPILE_WARNING

This compile-time warning warns users about deprecated syntax.

E_USER_ERROR

This user-defined error causes PHP to abort execution. User-defined errors (E_USER_*) are never caused by PHP itself, but are reserved for scripts.

E_USER_WARNING

This user-defined error will not cause PHP to exit. Scripts may use it to signal a failure corresponding to one that PHP would signal with E_WARNING.

E_USER_NOTICE

This user-defined notice may be used in scripts to signal possible errors (analogous to E_NOTICE).

7.2.5.2 Error Reporting

Several php.ini configuration settings control which errors should be displayed and how.

error_reporting (Integer)

This setting is the default error reporting for every script. The parameter may be any of the constants listed here, E_ALL for everything or a logical expression such as E_ALL & ~E_NOTICE (for everything except notices).

display_errors (Boolean)

This setting controls whether errors are displayed as part of PHP's output. It is set to On by default.

display_startup_errors (Boolean)

This setting controls whether errors are displayed during PHP startup. It is set to Off by default and is meant for debugging C extensions.

error_prepend_string (String)

This string is displayed immediately before the error message when displayed in the browser.

error_append_string (String)

This string is displayed immediately after the error message when displayed in the browser.

TRack_errors (Boolean)

When this setting is enabled, the variable $php_errormsg is defined in the scope PHP is in when an error occurs. The variable contains the error message.

html_errors (Boolean)

This setting controls whether HTML formatting is applied to the error message. The default behavior is to display HTML errors, except in the CLI version of PHP (see Chapter 16, "PHP Shell Scripting").

xmlrpc_errors (Boolean)

This setting controls whether errors should be displayed as XML-RPC faults.

xmlrpc_error_number (Integer)

This XML-RPC fault code is used when xmlrpc_errors is enabled.

log_errors (Boolean)

This setting controls whether errors should be logged. The log destination is determined by the error_log setting. By default, errors are logged to the web server's error log.

log_errors_max_len (Integer)

This is the maximum length of messages logged when log_errors is enabled. Messages exceeding this length are still logged, but are truncated.

error_log (String)

This setting determines where to place logged errors. By default, they are passed on to the web server's error-logging mechanism, but you may also specify a file name, or syslog to use the system logger. Syslog is supported for UNIX-style systems only.

ignore_repeated_errors (Boolean)

When enabled, this setting makes PHP not display the exact same message two or more times in a row.

ignore_repeated_source (Boolean)

When enabled, PHP will not display an error originating from the same line in the same file as the last displayed error. It has no effect if ignore_repeated_errors is not enabled.

Here is a good set of php.ini error-handling settings for development servers:

error_reporting = E_ALL
display_errors = on
html_errors = on
log_errors = off

Notices are enabled, which encourages you to write notice-safe code. You will quickly spot problems as you test with your browser. All errors are shown in the browser, so you spot them while developing.

For production systems, you would want different settings:

error_reporting = E_ALL & ~E_NOTICE
display_errors = off
log_errors = on
html_errors = off
error_log = "/var/log/httpd/my-php-error.log"
ignore_repeated_errors = on
ignore_repeated_source = on

Here, no error messages are displayed to the user; they are all logged to /var/log/httpd/my-php-error.log. HTML formatting is disabled, and repeating errors are logged only once. Check the error log periodically to look for problems you did not catch during testing.

The important thing to keep in mind is that error messages printed by PHP are meant for developers, not for the users of the site. Never expose PHP error messages directly to the user, catch the error if possible, and present the user with a better explanation of what went wrong.

7.2.5.3 Custom Error Handlers

Instead of having PHP print or log the error message, you can register a function that is called for each error. This way, you can log errors to a database or even send an email alert to a pager or to mobile phone.

The following example logs all notices to /var/log/httpd/my-php-errors.log and converts other errors to PEAR errors:

<?php

function my_error_handler($errno, $errstr, $file, $line)
{
if ($errno == E_NOTICE || $errno == E_USER_NOTICE) {
error_log("$file:$line $errtype: $errmsg\n", 3,
"/var/log/httpd/my-php-errors.log");
return;
}
PEAR::raiseError($errstr);
}

?>

7.2.5.4 Silencing Errors

Sometimes, you may wish to run your script with a high error level, but some things you do often produce a notice. Or, you may want to completely hide PHP's error messages from time to time, and would rather use $php_errormsg in another error-reporting mechanism, such as an exception or PEAR error.

In this case, you can silence errors with the @ statement prefix. When a statement or expression is executed with a @ in front, the error level is reduced to 0 for that statement or expression only:

<?php

if (@$_GET['id']) {
    $obj = new MyDataObject;
    $name = $obj->get('id', $_GET['id']);
    print "The name you are looking for is $name!<br />\n";
}

?>

When running this example with error_reporting set to E_ALL, a notice will be triggered if there is no 'id' index in the $_GET array. However, because we prefix the expression with the silencing operator @, no error message is displayed.

Custom error handlers will be called regardless of the silencing operator; only the built-in error displaying and logging mechanisms are affected. This is something you should be aware of if you define your own error handler, so your handler does not report silenced errors unintentionally. Because silenced errors have the error_reporting setting temporarily set to 0, we can use the following approach:

<?php

function my_error_handler($num, $str, $file, $line) {
    if (error_reporting() == 0) {
        // print "(silenced) ";
        return;
    }
    switch ($num) {
        case E_WARNING: case E_USER_WARNING:
            $type = "Warning";
            break;
        case E_NOTICE: case E_USER_NOTICE:
            $type = "Notice";
            break;
        default:
            $type = "Error";
            break;
    }
    $file = basename($file);
    print "$type: $file:$line: $str\n";
}


set_error_handler("my_error_handler");

trigger_error("not silenced error", E_USER_NOTICE);
@trigger_error("silenced error", E_USER_NOTICE);

?>

Here, we check the current error_reporting setting before displaying the error message. If the error_reporting is 0, the custom error handler aborts before printing anything. Thus, the silencing is effective even with our custom error handler.

    Team LiB
    Previous Section Next Section