Categories


Archives


Recent Posts


Categories


A Survey of PHP Error Handling

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

This entry is part 1 of 3 in the series PHP Error Handling. Later posts include Magento's Mini Error Framework, and Laravel's Custom Error Handler.

It’s a daily event across the PHP development world. Somewhere, someone is thinking, and possibly typing

I updated the code and it stopped with no error

This sort of question used to trigger my inner programmer-rage-monster because there’s almost always an error message. While there’s a few legitimate cases where a programming language’s error messaging system may short circuit, it’s far more common that a poorly configured system is to blame.

The error messages are there, you just need to know how to find them.

I say this used to trigger my inner programmer-rage-monster, because it turns out that the myriad different ways of configuring error reporting in PHP means you can never be sure if error reporting and logging is behaving the way you think it should.

This article attempts to, if not reign in the PHP error reporting white screen monster, then to provide a road map for successfully debugging errors on your PHP system.

What’s an Error

First off, let’s define what we mean by a PHP error. Let’s say you’re building an accounting system and discover it’s incorrectly doing a revenue split. This is not a PHP error. This is a logic error in your application. PHP is doing exactly what you told it to, it’s just that the problem was more complicated than you thought.

A PHP error is when you write a piece of code PHP can’t understand. This might be something that’s explicitly disallowed by the system (calling a member function on a non-object, for example), or may be something that the PHP team has decided is a bad idea (using a variable before it’s been defined). Either way, a PHP error is something wrong with your code and not something wrong with your application.

Some errors make PHP stop in its tracks. For example, leaving off a semi-colon at the end of a line

echo $foo
echo $foo;

will trigger a parsing error. If PHP can’t parse the file, it has no idea what to do, so it stops. It’s the same with the dreaded “call to a member function on a non-object” error

$foo = 1;
echo $foo->render();

The above code just tried to call a method (render) on an integer — which makes no sense in PHP. Again, PHP halts execution.

There’s a second class of errors php can recover from. These are often the result of syntax that used to be valid PHP, but in retrospect, syntax the PHP team decided was a bad idea. This is an example of PHP’s backwards compatible philosophy. The old bad behavior is still allowed, but PHP will raise an error, and then continue with the program. This lets old “bad” code continue to do what it did, but gives more disciplined developers path out of the muck.

For example, the following code results in a PHP error

#File: example.php
<?php    

echo $foo;
echo "Done","\n";

since $foo is undefined.

$ php example.php 

Notice: Undefined variable: foo in /path/to/example.php on line 8
Done

Even though echo "Done"; happens after this error, PHP still outputs the text Done, because an Undefined variable error is a recoverable error.

Regardless of whether PHP can recover from an error, it generates an error message. Consider the above error

Notice: Undefined variable: foo in /path/to/example.php on line 8

The error message here is Undefined variable: foo in /path/to/example.php on line 3. This error message gives us a short description of the error (or what PHP has identified as the error), the file the error occurred in (example.php), and the line number where the error occurred (8).

While these error messages are useful, sometimes they’re misleading. For example, the following code

#File: example.php
<?php    
if($foo == 'bar') 
    echo $test;
}

produces the error

Parse error: syntax error, unexpected '}' in /path/to/example.php on line 3

At first blush this error makes no sense — why wouldn’t an if clause expect a closing } — plus we have one?

The reason for this message (which some of you may have spotted), is the PHP parser saw this code more like this

if($foo == 'bar') echo $test;
}

That is, a single clause if statement, followed by a rouge }. To a human, the error was the missing opening bracket ({), but a code parser can’t see this.

While these error messages are useful, you’ll still need to use your brain and examine/analyze why a particular piece of code is causing a problem.

Error Logging

Now that we know what an error is, you’re probably wondering where PHP puts these error messages. There’s multiple places a PHP error message may end up, depending on your system’s configuration.

First is the error logging. If the php.ini value log_errors is set to true, PHP will write the error message to a log file.

log_errors = On

This value is also configurable at runtime via ini_set.

ini_set('log_errors',1);

so changing your php.ini file may not be enough depending on the framework/application code.

The log_errors ini flips logging on and off. The error_log ini setting controls which file PHP will log its error messages to.

error_log = /tmp/php_errors.log

You may also set this value at runtime with ini_set

ini_set('error_log','/tmp/log2.log');

So again, you application or framework may be changing this file’s location after you’ve set a value in php.ini

Many stock installations of PHP do not set an error_log value.

error_log -- no value

If your error_log value is blank, PHP will pass the error message onto the web server. This means your PHP error message will end up in your web server’s error log. In apache, this is configured (in httpd.conf or one of its include files) with

ErrorLog "/var/log/apache2/error_log"

If you’re having trouble finding your system’s apache log, try doing something like the following at the start of your program or right before the point in your code that’s causing an error.

ini_set('log_errors','1');
ini_set('error_log','/tmp/my-custom-php-error-log.log');

These two ini settings will both ensure error logging is on, as well as set a custom error log file in your system’s tmp folder.

Finally, make sure your web server has the proper permissions to write to the error_log file you’ve specified. If the web server user lacks these permissions, PHP will fall back to logging to the web server’s error log.

Error Printing

In addition to the error log, messages are also printed to the browser, or “standard output” (STDOUT) if running in command line (CLI) mode.

Notice: Undefined variable: foo in /path/to/example.php on line 2

The display of a PHP error is controlled by the display_errors ini setting.

display_errors = 1

Like the other ini settings we’ve discussed, you may also set this at runtime.

ini_set('display_errors', '1');

If we add this to out magic “show me the errors” ini recipe, we’ll end up with something that looks like this

ini_set('log_errors','1');
ini_set('error_log','/tmp/my-custom-php-error-log.log');
ini_set('display_errors', '1');

If you use PHP from the command line, you may have noticed it often prints errors twice.

PHP Notice:  Undefined variable: foo in /path/to/example.php on line 5

Notice: Undefined variable: foo in /path/to/example.php on line 5

That’s because the command line version of PHP is both logging and displaying the errors. If the error_log value is not set, a logged error is sent to the shell’s STDERR stream, and then the “displayed” error (as per normal) is sent to the shell’s STDOUT stream.

This also draws attention to a slight difference between a logged error and a displayed error. A displayed error starts with the string PHP Notice, but a logged error starts with the string Notice. The leading PHP is included in the logged error to help system administrators tell which system is generating this particular error, while a displayed error drops this leading PHP, assuming the developer will know what language they’re using.

So, you’ve got everything configured correctly, but your errors still aren’t showing up. Nothing in the error log, and nothing in the browser, but we still have a halting, or “white screen of death” application. What gives?

The likely culprit is the PHP “error reporting” feature.

Error Reporting

In addition to configuring where errors are reported, PHP (being PHP) lets you configure what errors are actually considered errors in your system. This is done via the error_reporting function or error_reporting ini settings.

For example, lets consider our invalid code from before

error_reporting(E_ALL | E_STRICT);  //trust us on this line for now
echo $foo;
echo "Done","\n";

If you you run the above code, you’ll end up with an error like this

Notice:  Undefined variable: foo in ...

However, consider this almost identical code

error_reporting(E_PARSE);  //trust us on this line for now
echo $foo;
echo "Done","\n";

Run this, and PHP will not display and will not log the error. We’ve effectively told PHP to ignore all PHP Notice errors (and a slew of others). Using a variable before it’s defined is, effectively, no longer an error on our system.

Of course, if other code expects that variable to be defined you’ll end up with a weirdly behaving program — which is why PHP considers this worth raising as an error by default.

Error Levels

The error_reporting function works by assigning every possible PHP error an “error level”. Current versions of PHP ship with 16 different error levels.

E_ERROR
E_WARNING
E_PARSE
E_NOTICE
E_CORE_ERROR
E_CORE_WARNING
E_COMPILE_ERROR
E_COMPILE_WARNING
E_USER_ERROR
E_USER_WARNING
E_USER_NOTICE
E_STRICT
E_RECOVERABLE_ERROR
E_DEPRECATED
E_USER_DEPRECATED
E_ALL

An E_ERROR is the most serious type of error — a fatal PHP error. Next is E_WARNING, etc., all the way up to E_USER_DEPRECATED. E_ALL is a special case that turns on all the errors, and we’ll discuss it fully later on. By putting every error into a specific error level, PHP lets you (the user) decide which errors you want to see. Only want to see fatal errors? Set the level to E_ERROR

error_reporting(E_ERROR);

In addition to setting the error reporting level at runtime with the error_reporting function, you may also set the level with the error_reporting ini value in a php.ini file

; PHP's `ini` parser will understand the error constants
error_reporting = E_PARSE;

Or via the ini_set function

ini_set('error_reporting', E_PARSE);

Error Reporting Gotachas

There are, of course, some gotchas. The one that’s probably on your mind is the error reporting level we used in our example script

error_reporting(E_ALL | E_STRICT); 

What’s that E_ALL | E_STRICT all about? There’s two things going on here. The first is PHP’s bitwise operator constant system. E_ALL, E_STRICT, etc. are PHP constants that resolve to particular numbers. These numbers are the actual error levels. Like many PHP constant systems, the error_reporting mechanism allows you to combine certain constants using bitwise operators. We’ll describe this system in the Bitwise Constant System below — it’s an “AP” level topic that you don’t need to fully comprehend to move forward — just know that we’re saying “show us all all the errors” when we say error_reporting(E_ALL | E_STRICT);

More important though is, why is this necessary? Shouldn’t E_ALL be enough? In versions of PHP 5.4 and above, it is. Unfortunately, in previous versions of PHP, E_STRICT errors were not included in the special E_ALL error reporting level. This was a classic PHP internals compromise. The E_STRICT level was introduces to

“have PHP suggest changes to your code which will ensure the best interoperability and forward compatibility of your code.”

This was, in part, an effort to rid PHP of certain weird behaviors that had been preserved for backwards compatibility reasons. Consider something like this

class A
{
    public function test()
    {
        echo "Hello PHP — weep for me.","\n";
    }
}    
echo A::test();

A modern PHP programmer would look at this and cringe. We’re using a static calling pattern (A::test()) to call a non-static function. This should clearly be an error. However, in PHP 4, which had no concept of static member functions, the above syntax was valid, (and introduced to allow users the ability to use methods on a class that didn’t rely on object state. PHP 4 was weird — tread lightly).

PHP 5.3 made this an E_STRICT error. In practice this meant when PHP 5.3 was released to the world many applications which had set their error_reporting level to E_ALL suddenly found themselves inundated with all sorts of E_STRICT warnings. To make life easier for these folks, E_STRICT errors were removed from E_ALL. This solved some specific problems, at the cost of further complicating an already complicated system.

Long story short: If you want PHP 5.3 to report all your errors, use E_ALL | E_STRICT. If you’re interested in the nitty gritty details of PHP’s bitwise constant system, see the Bitwise Constant System section below

Before moving on, let’s revisit out SHOW ALL THE ERRORS code snippet and add the error_reporting call.

error_reporting(E_ALL | E_STRICT); 
ini_set('log_errors','1');
ini_set('error_log','/tmp/my-custom-php-error-log.log');
ini_set('display_errors', '1');

Custom Error Handling

So, that’s a pretty long list of things to keep track of for PHP error reporting. Surely that’s everything, right? Right?

Of course not! While the error level system allows a fine grained control over which errors show up, it’s not enough for some users (especially if they don’t understand the bitwise constant system. For these users we have the set error handler function.

The set_error_handler function allows an end user to define their own error handling function. It accepts a PHP callback/callable pseudo type, which indicates which function, anonymous function, static method, or object method, should be called when a PHP error happens.

Consider a modified version of our basic script above

#File: example.php
function ourMagicErrorHandler($error_level, $error_string, $error_file, $error_line, $error_context)
{
    echo "An error happened but we're not going to say which one","\n";
    return true;
}
set_error_handler('ourMagicErrorHandler');

error_reporting(E_ALL | E_STRICT);  //trust us on this line for now
echo $foo;
echo "Done","\n";

Here we’ve set the error handler as our custom function ourMagicErrorHandler. If we run this code (which still contains the undefined variable error), we’ll see the following.

$ php example.php 
An error happened but we're not going to say which one
Done

PHP’s normal error handling has been completely suppressed in favor of our own. If we want normal PHP error handling to resume after our custom error handler call, we should return false.

echo "An error happened but we're not going to say which one","\n";
return false;

Running our script with the above would product output like this

$ php example.php 
An error happened but we're not going to say which one
PHP Notice:  Undefined variable: foo in /path/to/example.php on line 11
Done

Fatal errors are an important caveat to this. While a fatal error will still be caught by a custom error handler, PHP will log that fatal error regardless of the custom error handler returning true or false.

Custom error handlers are used to create customized error output. If you examine the parameters of the handler method, you’ll see PHP passes in all sorts of useful information we can use to create our own error messages, and better pin-point an error.

The other reason many frameworks set a custom error handler is to create a use strict mode for PHP where all errors halt execution, not just fatal errors. Consider the following error handler

#File: example.php
function ourMagicErrorHandler($error_level, $error_string, $error_file, $error_line, $error_context)
{
    echo $error_string,"\n";
    echo "on line ", $error_line, "\n";
    echo "in file ", $error_file, "\n";        
    exit(1);
    // return false;
}

Running our script with this in place would produce the following output

$ php example.php 
Undefined variable: foo
on line 14
in file /path/to/example.php

That is, a descriptive error message would be output, but execution would halt thanks to our exit statement, despite this being an error PHP may normally recover from. Using an error handler like this when you’re developing code (vs. a production system) helps you fail fast, and prevents the slow growth of PHP code rot in your system.

Unfortunately, this sort of error handler often runs into trouble in the real world, as many useful PHP libraries produce myriad recoverable error messages. (Ask the Concrete 5 folks about ADODB)

Custom Error Handler Gotchas

There are, of course, some gotchas.

Per the manual,

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

Since our custom error handler handles the errors at runtime, having the above errors in the same file as set_error_handler would prevent set_error_handler from being defined. Therefore there’s no way for a custom error handler to handle these errors in the same file.

Many novice, or even intermediate, developers get the idea to log errors in a custom error handler. While there’s nothing stopping you from doing this, the above caveat should make it clear that doing so will not catch all the errors in your system, and monitoring the real PHP error logs is still essential.

Checking for a Error Handler

When you start working with a PHP system you’re unfamiliar with, sometimes you’ll want to know if there’s a custom error handler running. Unfortunately, there’s no get_error_handler function — instead you need to rely on some awkward code to get at the information you want.

When you set a custom error handler

$result = set_error_handler(array('Valid', 'callback'));

the return value for set_error_handler is the error handler the function is replacing. If there’s no error handler set, set_error_handler will return null. So, to check for an error handler just set another one.

This, of course, means you’ve just changed the system behavior for the rest of the request (which may not be what you want). Fortunately, the restore_error_handler function will let you undo the last call to set_error_handler.

While it’s a little bit childish and stupid not the most intuitive pattern in the world, but then again, so is high school it does allow you to check for an error handler.

Exceptions

Some of the more object oriented among you may be scratching you heads and wondering

This all seems very procedural, and C/Go like. I thought PHP was a modern object oriented language with exceptions

Well, you’re right. This is very procedural and C/Go like. However, PHP does have exceptions — they just live alongside the PHP error system.

Like so many of the weirder things in PHP, this goes back to the transition from PHP 4 (with a basic object system) to PHP 5 (let’s make PHP a java-like OOP system). Exceptions were introduced in PHP 5, but with all the pre-existing legacy PHP code in the wild, there was little chance the PHP error system would be retired.

So, how do exceptions and errors co-exist? A caught exception won’t interact with the PHP error system at all. Catch you exceptions, handle them as your wish, and you won’t have anything to worry about.

However, an uncaught exception will create a fatal PHP E_ERROR error, and php will halt execution (fatal errors are non-recoverable errors). One caveat: This uncaught exception fatal error will not be handled by a custom error handler.

The philosophy behind this is that an uncaught exception should always produce a crash, because exceptions should always be caught, and if they’re not it’s a bug in the program that needs to be fixed. Without any additional context, this makes sense — but that philosophy also assumes exceptions are your only method for handling errors.

Programming exception theory is complicated, controversial, and (in my opinion) ultimately dependent on the assumptions and theory-of-operation in your system. The Go language eschews exceptions in favor of error codes, and they have good, practical reasons for doing so. However, that decision doesn’t invalidate python’s decision to use exceptions to handle normal expected (i.e. unexceptional) error states. Both language designers have their reasons for those choices, and so long as they’re internally consistant and respectful of their philosophy, neither choice is “right” or “wrong”.

Unfortunately, given PHP’s “kitchen sink” theory of design, these sorts of confusing edge cases are inevitable. Should a fatal error caused by an exception be handled by a custom error handler? If you’re a native PHP developer, you probably assume it should — if you’re coming from Java, you probably assume it shouldn’t. If you’re coming from Go you’re like OMG why do I need to write PHP. Without an overriding theory of operation, these decisions can appear arbitrary — just a list of rules you need to memorize.

Further confusing things? There actually is a way to listen for uncaught exceptions with the set_exception_handler function

function ourMagicExceptionHandler($e)
{
    echo 'Someone threw an exception (type: <code>' . 
    get_class($e) . '</code>) and no one caught it.';
    return true;
}    
set_exception_handler('ourMagicExceptionHandler');
throw new Exception("Foo");
echo "Done";

However, while this function will allow you to “catch” an uncaught exception, the program will still terminate after running your handler code.

Confusing? Inconsistent? Counter intuitive? Multiple visions making a system overly complicated? From a certain point of view, yes, yes, yes and yes. That, unfortunately, is just how things are in PHP land. If you want PHP’s benefits, you need to live with PHP’s poor lack of design decisions. Hopefully the above is enough to help you come up with your own systems for dealing with errors and exceptions, or decipher the meaning behind the systems you’re been hired to work with.

Bitwise Constant System

As promised, we’re going to talk about PHP’s bitwise constant system. Our interest is in the error_reporting constants, but PHP uses similar systems in many other places.

We’ll start with a quick CS-101 review of binary numbers. Unless you’re a robot or an alien, when you think of a number like, say 102, you’re probably thinking in “base 10”. For example, the base 10 number 102 has

  1. A 2 in the ones place
  2. A 0 in the tens place
  3. A 1 in the hundreds place

for an end result of 102 different things. We typically don’t think of numbers in this way, but mathematicians need to because there’s number systems other than base 10. Of particular interest to computer science people are binary, or base 2 numbers. Instead of a ones place, tens place, hundreds place, etc., in base two a number has a ones place, a twos place, a fours place, an eights place, etc. So, the decimal number 102 is represented in binary as

1100110

That’s

  1. 0 in the ones place
  2. 1 in the twos place
  3. 1 in the fours place
  4. 0 in the eights place
  5. 0 in the sixteens place
  6. 1 in the thirty-twos place
  7. 1 in the sixty-fours place

So, that’s 64 + 32 + 4 + 2 = 102. Using just ones and zeros, we’ve represented one hundred and two different things.

The reason binary numbers are important to programmers is, at the end of the day, everything in a computer is ultimately represented in an on or off state. The RAM and the hard drives (or hard drives made of RAM) are all microscopic registers for storing a 1, or storing a 0. In this day and age you can write programs without being aware of these binary systems. However, at the dawn of computers, it would have been impossible to write a significant program without knowing about and manipulating these binary systems. Additionally, even in this day and age, there’s performance benefits to be gained by sticking to pure binary numbers.

PHP is implemented in C and C++, both languages forged during the dawn of computers. You may see where this is heading.

Earlier we talked about PHP’s error levels. What we glossed over was that each of these constants is actually a number. For the most recent version of PHP, these numbers are

1        E_ERROR (integer)
2        E_WARNING (integer)
4        E_PARSE (integer)
8        E_NOTICE (integer)
16        E_CORE_ERROR (integer)
32        E_CORE_WARNING (integer)
64        E_COMPILE_ERROR (integer)
128        E_COMPILE_WARNING (integer)
256        E_USER_ERROR (integer)
512        E_USER_WARNING (integer)
1024    E_USER_NOTICE (integer)
2048    E_STRICT (integer)
4096    E_RECOVERABLE_ERROR (integer)
8192    E_DEPRECATED (integer)
16384    E_USER_DEPRECATED (integer)
32767    E_ALL (integer)

In decimal (another word for base 10), these numbers don’t make much sense. Some numbers appear to be higher than others, so the lower the number the more serious the error? But E_PARSE seems more serious than E_WARNING. Besides that confusion, the gaps in the numbers seem arbitrary.

If we start to look at these numbers in binary, a pattern emerges.

000000000000001 E_ERROR (binary)
000000000000010 E_WARNING (binary)
000000000000100 E_PARSE (binary)
000000000001000 E_NOTICE (binary)
000000000010000    E_CORE_ERROR (binary)
...
100000000000000    E_USER_DEPRECATED (binary)    
111111111111111 E_ALL (binary)

Each incremental error level shifts the 1 one column to the left in binary format. With that in mind, let’s consider our now ubiquitous variable undefined error. The following program produces a Notice: Undefined variable: foo error

<?php    
error_reporting(E_ALL);
echo $foo;

This is as expected, since E_ALL is the constant we’re supposed to use if we want all errors reported. Also, if we set a lower error reporting level

error_reporting(E_ERROR);
echo $foo;

no error will be produced. However, if we set the error level to E_CORE_ERROR, which is one level higher than E_NOTICE,

#File: example.php
<?php    
error_reporting(E_CORE_ERROR);
echo $foo;
echo "Done\n";

we still won’t see any errors.

$ php example.php
Done

It’s only if we set the level to E_NOTICE that an error will be produced.

error_reporting(E_NOTICE);
echo $foo;

The common misconception with PHP error levels is you’re telling PHP

Report errors this level or higher/lower

That’s not how the error constants work. Instead, each “place” in the binary number tells PHP which error we want to report. For example, if PHP says to itself

Hey, I need to issue a notice

It checks the binary version of the error reporting number, and looks at the fourth column

000000000001000

If the fourth column is a 1, it produces the error. If the fourth column is a 0, it does not produce an error. That’s why both E_CORE_ERROR and E_ERROR

000000000000001 E_ERROR (binary)
000000000010000    E_CORE_ERROR (integer)

failed to produce a notice. Their fourth column/place was 0, which PHP interpreted as “Don’t show a Notice“.

Why create something so obtuse, and (seemingly) only able to display errors of one type? Because it’s actually a flexible and powerful system, and using binary numbers (theoretically) brings an efficiency to the PHP internals code. We’re just missing one key feature.

Let’s say you wanted to only show errors of type E_ERROR or E_CORE_ERROR. If you tried this

error_reporting(E_ERROR);

You’d miss the E_CORE_ERROR errors. If you did this

error_reporting(E_CORE_ERROR);

You’d miss the E_ERROR errors. However, if you consider the binary number

000000000010001

That is, a number with both the first (E_ERROR) and the fifth (E_CORE_ERROR) columns set to 1, this number would tell PHP to show both the E_ERROR AND the E_CORE_ERROR errors. The binary number 000000000010001 can be represented as the decimal number 17. So, if you set the error reporting level like this

error_reporting(17);

you’ll be set. With the first column set to 1, PHP will report E_ERROR errors. With the fifth column set to 1 PHP will report E_CORE_ERROR errors. With every other column set to 0, no other errors will be reported.

Of course, while powerful, the need to sit down and convert things to and from binary is a bit of the pain in the behind. Also, the PHP manual strongly discourages you from using raw integers in the error_reporting function/ini. Fortunately, you can take a shortcut. The above examples are equivalent to.

error_reporting(E_CORE_ERROR | E_ERROR);

If you var_dump(E_CORE_ERROR | E_ERROR), you’ll see the value of the expression E_CORE_ERROR | E_ERROR is 17. In english, the expression E_CORE_ERROR | E_ERROR can be translated as

Report any error that’s an E_CORE_ERROR or an E_ERROR

As to why this works, we’ll need to take a detour into bitwise operators.

Bitwise Or

We’ve already talked a bit about how binary numbers are beloved by a certain type of programmer. These numbers are so beloved, there’s a group of special operators for dealing with binary numbers, similar to addition, subtraction, multiplication and division for all numbers. PHP, being the kitchen sink language that it is, supports these bitwise operators.

While it’s useful to learn all the bitwise operators, the one we’re going to talk about today is the “Or (inclusive or)” operator, represented by the | character. Per the manual, this operator works on two numbers ($a | $b) and

Bits that are set in either $a or $b are set.

Still a little lost? Well, let’s consider two numbers, 17 and 24. If you “bitwise inclusive or” these numbers

echo 17 | 24;

you’ll end up with 25. When viewing these numbers as decimals, it makes no sense. However, if we view the numbers as binary

10001 (17 in base 10)
11000 (24 in base 10)

we have two “5 column” binary numbers, or two numbers with 5 bits each. A “bitwise or” returns bits that are set in either number, meaning a “bitwise or” of 10001 and 11001

 10001
 11000 
------
 11001

will return a 1 in the fifth, fourth, and first columns, or 11001. The binary number 11001 is equivalent to the decimal number 25. Therefore, 17 | 24 = 25.

Why do we care? Think back to the error_reporting call

error_reporting(E_CORE_ERROR | E_ERROR);

Ah ha! It turns out we were using bitwise operators here. The expression E_CORE_ERROR | E_ERROR is equivalent to 16 | 1. If you consider the binary forms of these numbers

 000000000000001 E_ERROR (binary)
 000000000010000 E_CORE_ERROR (binary)
--------------------------------------------------
 000000000010001 (our final result)

You can see how the “bitwise or” results in the number 10001 (or 17 in decimal). It also becomes clear why the specific numbers were chosen for each error level

000000000000001 E_ERROR (binary)
000000000000010 E_WARNING (binary)
000000000000100 E_PARSE (binary)
000000000001000 E_NOTICE (binary)
000000000010000    E_CORE_ERROR (integer)
...

By spacing the number as they did, the original PHP developers made it relatively easy to specify any set of errors you want with a “bitwise or”. If you wanted to turn on all the PHP errors, you could do something like this

error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | 
E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_STRICT | 
E_RECOVERABLE_ERROR | E_DEPRECATED | E_USER_DEPRECATED);    

By combining all the errors with a bitwise operator, we end up with a binary number of

111111111111111

which is equal to 32767, which (in no coincidence) is also the value of the E_ALL constant. E_ALL is a shortcut for adding all the errors, and not an error level in and of itself.

A few of you may be wondering about the following statement, used earlier in this article

error_reporting(E_ALL | E_STRICT);

This is needed for PHP 5.3. In PHP 5.3 E_ALL didn’t include E_STRICT errors for backwards compatibility reasons. In our new mathematical language, in PHP 5.3 E_ALL was equal to 30719

32767 in PHP 5.4.x, 30719 in PHP 5.3.x, 6143 in PHP 5.2.x, 2047 previously

In binary, that’s

111011111111111

Notice anything? The twelfth column is a 0. This is the column that controls E_STRICT errors. E_STRICT is equal to 2048, or 000100000000000 in binary. Line up the two numbers for a “bitwise or”

 111011111111111
 000100000000000
----------------
 111111111111111

and you get the magic “SHOW US ALL THE ERORS” number, 111111111111111. In later versions of PHP (5.4+), this E_STRICT special case was removed, and E_ALL was changed to 111111111111111, ensuring E_ALL truly contains ALL the errors.

Error Miscellany

While we’ve put a dent in the surface of PHP’s various error systems, there’s still plenty of other bits of miscellany to trip up any newcomer. The rest of this article will cover some of the more common cases you may need to deal with.

Runtime vs. php.ini

Many of the configuration settings that control error handling can be set via a php.ini file, or via runtime functions like error_reporting or ini_set. While the runtime functions are incredibly useful, they do create an enormous amount doubt when you’re seeing some weird behavior but no corresponding PHP error message on the screen or in a log. Since the way PHP handles errors can change at anytime, you’re never 100% sure you’re seeing all the errors.

As mentioned previously, my way of dealing with this is to temporarily add the following code as close as I can to the potential error.

error_reporting(E_ALL | E_STRICT); 
ini_set('log_errors','1');
ini_set('error_log','/tmp/my-custom-php-error-log.log');
ini_set('display_errors', '1');

However, this isn’t 100% fool proof. PHP has the ability for an administrator to disable functions. Many managed (or managed-ish) hosting companies will disable the ini_set function, or even the error_reporting function as a security precaution. Outside of this feature, there’s a slew of public and private PHP security extensions that limit what can, and can’t, be set at runtime.

Just one more piece of trivia to keep under your hat if you’re a PHP developer.

Finding php.ini

Sometimes, especially when you’re dealing with an unfamiliar system, it’s difficult to track down the php.ini file you’re looking for. This is complicated by the fact PHP can load multiple ini files. The location of PHP’s ini file is set when PHP is compiled (it’d be pretty hard to have an ini setting for the location of php.ini, no?).

In addition to this main php.ini, PHP will also scan an extra directly (also set at compile time) for additional files ending in ini. If any are found, PHP will load them after it loads the main php.ini file.

Fortunately, PHP has a mechanism for displaying these ini files. If you’re running PHP from the command line, just pass in the --ini flag

$ php --ini
Configuration File (php.ini) Path: /usr/local/php5/lib
Loaded Configuration File:         /usr/local/php5-20130127-205115/lib/php.ini
Scan for additional .ini files in: /usr/local/php5/php.d
Additional .ini files parsed:      /usr/local/php5/php.d/10-extension_dir.ini,
/usr/local/php5/php.d/50-extension-apc.ini,
/usr/local/php5/php.d/50-extension-curl.ini,
/usr/local/php5/php.d/50-extension-intl.ini,
/usr/local/php5/php.d/50-extension-mcrypt.ini,
/usr/local/php5/php.d/50-extension-memcache.ini,
/usr/local/php5/php.d/50-extension-memcached.ini,
/usr/local/php5/php.d/50-extension-mongo.ini,
/usr/local/php5/php.d/50-extension-mssql.ini,
/usr/local/php5/php.d/50-extension-oauth.ini,
/usr/local/php5/php.d/50-extension-pdo_dblib.ini,
/usr/local/php5/php.d/50-extension-pdo_pgsql.ini,
/usr/local/php5/php.d/50-extension-pgsql.ini,
/usr/local/php5/php.d/50-extension-solr.ini,
/usr/local/php5/php.d/50-extension-twig.ini,
/usr/local/php5/php.d/50-extension-uploadprogress.ini,
/usr/local/php5/php.d/50-extension-xdebug.ini,
/usr/local/php5/php.d/50-extension-xhprof.ini,
/usr/local/php5/php.d/50-extension-xsl.ini,
/usr/local/php5/php.d/50-extension-xslcache.ini,
/usr/local/php5/php.d/99-liip-developer.ini

and you’ll get a list of every php.ini file loaded, as well as their location.

If you’re running PHP from a web server context, the phpinfo() function will output similar information

image of phpinfo function call

Of course, that’s the simple case — and nothing is ever that simple with PHP.

Earlier in this article we covered setting php.ini settings in an .ini file, and at runtime with the ini_set functions. It’s also possible for ini settings to be set in the web server configuration. Assuming you’re using apache, this means in httpd.conf as well as .htacess with syntax like this

php_value display_errors 1

Additionally, PHP 5.3 introduced the .user.ini files. This system allows you to create a .user.ini file in a web server directory, and PHP will parse this file for ini values. Despite ending in .ini, the format for this file is the same as the .htaccess format listed above.

Finally, while not strictly a php.ini file, PHP has a special ini configuration value name auto_prepend_file. This setting allows you specify a PHP file to be parsed before the main PHP file is parsed. While you can drop anything you like into this file, I’ve encountered many multi-user setups where each individual developer has their own auto_prepend_file to set developer only configuration settings at runtime. If a full path isn’t used for this value, PHP will search the include_path for a file.

Custom PHP Errors

PHP has a function you can call to trigger your own errors. By default these errors are issued at the E_USER_NOTICE level, but you can change that using the method’s second parameter.

trigger_error('This is a custom notice.',E_NOTICE);

The trigger_error function was introduced in the PHP 4 days to give PHP some exception-like, (or is that lite?), behavior — of course now that PHP has an exception system this secondary system may seem redundant to some, while others may find it useful to use in concert with the exception system. This message from the manual makes it clear where the PHP documentation teams falls on that subject

This function is useful when you need to generate a particular response to an exception at runtime.

Or maybe the PHP documentation team isn’t using exception to mean a PHP exception, but instead as the english language “something out of the ordinary happened” usage.

Programming is hard kids — don’t let anyone tell you otherwise.

Additional Error Logging

There’s two additional error logging functions a PHP developer should be aware of: error_log and syslog.

Per the manual, the error_log function “Send[s] an error message to the defined error handling routines”. This doesn’t mean PHP will raise a PHP error. If you’re running PHP in a web context, the message is sent directly to the error log. If you’re running PHP in a command line context, calling error_log will also send a message to the error log, unless the error log isn’t set. If the error_log ini isn’t set, then the command line PHP will send the message to STDERR.

The error_log function will perform its duties regardless of the value set in the log_errors ini setting. Because of this, error_log() is often used by frameworks to ensure a message is sent to the error log. Despite ignoring log_errors, this function will respect the error_log ini that sets a log file location, (defaulting to the web server log if no value is set in error_log.

The syslog function is similar, in that it sends a messages directly to a logging system. However, instead of the PHP error log, or a web server error log, this function sends a message directly to the operating system’s system log. If you’re interested in learning more about this, checkout the manual page

$ man syslog.conf

You’ll mostly see this used in private PHP frameworks where one of the developers is also a unix system administrator and wants all their messages in the same place. Most modern (or even middle aged) PHP frameworks tend to abstract their own systems away from a specific system like *nix‘s syslogd.

Errors During eval

PHP, like most “end user-programmers don’t need to compile things” languages has an eval function. An eval function lets a user create a string that’s valid PHP code, and then run the string as a mini-program. There’s security concerns around the use of eval which make it one of those hot button topics in programming circles, but we’re not here to talk about that. If you’ve decided to use eval, or your framework-of-choice-or-fiat uses it, there’s a few things w/r/t to errors you’ll need to keep in mind.

An error in a string of evaled code will still be sent to a custom error handler set in the main PHP program with set_error_handler.

However, when PHP creates error messages for the evaled code, the line number referenced in that error string will refer to the line in the eval’d block, and not the line number in the main program where eval is called. For example, this code

<?php    
echo "Foo";
echo "Baz";
eval('echo $foo;');    

will issue the following error message

PHP Notice:  Undefined variable: foo in /path/to/example.php(4) : eval()'d code on line 1

The line 1 refers to the line in the eval string. If you want the line of the PHP program where eval was called, look for the number in parenthesis (in this case 4)

Also, remember how a custom handler can’t handle certain error types (including E_PARSE) generated in the same file? This is true even if the code executes in an eval block.

The Last Error: $php_errormsg

As mentioned earlier, there are many errors which PHP can recover from. After handling an error, PHP will populate a variable named $php_errormsg in the current scope, ensuring $php_errormsg always contains the latest error, (unless you leave that current scope, of course) I’ve seen many custom error handling routines that make some use of this variable, so you’ll want to be aware of it.

Also, if you’re using set error handler and return true (meaning you’re skipping the default PHP error handling), then PHP will not populate this variable.

Another caveat: The php.ini setting track_errors must be true for this variable to be populated.

The Error Control Operator

PHP has an error control/surpression operator. Place the operator before any code that might cause an error, and PHP will swallow the error whole. For example, running this script should produce an undefined variable foo Notice

function main()
{
    echo $foo;
}
@ main();

But because of the leading @, no error is produced. Unfortunately, these errors are almost completely swallowed. They’re not displayed, and not logged.

The only trace the error happened will be the population of the previously mentioned $php_errormsg variable. So, in theory, it’s still possible to detect and process a suppressed error, but given $php_errormsg is only available for the scope in which the error occurred, and it’s impossible to tell when the error in $php_errormsg was generated without extra coding gymnastics, the usefulness of this construct is limited.

Most professional PHP developers I know and respect eschew the @ handler whenever possible. Unfortunately, given a 10 second code change will completely swallow an error that might take an entire day to track down, you’ll often see code bases littered with the @ operator. These are also the projects that, inevitably, end up producing the most bizarre and hard to track down errors, given the undetectable-unless-you’re-already-looking-at-it nature of @.

Error Control and Custom Error Handlers

The other way an error control operator suppressed error might show up is in a custom error handler. Your custom error handler will be called for a suppressed error. However, per the manual

If you have set a custom error handler function with set_error_handler() then it will still get called, but this custom error handler can (and should) call error_reporting() which will return 0 when the call that triggered the error was preceded by an @.

If you call the error_reporting function without passing in a level, it will return the current error reporting level. However, the error control operator changes this behavior, and within your custom error handler error_reporting will be reported as 0 if the error control operator was used. The manual text quoted here suggests your custom error handler should respect this setting. Most of the custom error handlers I’ve seen in the wild do not respect this setting. “The right” thing to do here is left as an exercise for the reader.

Backtrace

While not strictly an error handling mechanism, the php functions debug_backtrace and debug_print_backtrace are often used in a custom error handler to print out a PHP call stack. A PHP call stack will list the chain of methods/functions called to reach a particular depth/point in a program — which is useful debugging information.

Be careful using these functions in systems where objects have circular references, or contain a massive amount of information. The call stack, by default, contains a dump of every parameter variable used, and the display of circular object references, or just large objects, will eat up all the memory available unless you’re using something like xDebug to control the depth displayed

xdebug.var_display_max_depth = 3

What’s xDebug

Speaking of which, if you’re doing any sort of PHP development work (as opposed to developing within a PHP based system), you probably want to install xDebug. While a full feature rundown of xDebug is beyond the scope of this article, xDebug does alter PHP’s handling of error messages. With xDebug enabled, every PHP error will include a full call stack of the code that led to the error. For example, without xDebug the following program

#File: example.php
<?php    
function main()
{
    echo $foo;
}
main();

would produce the error

Notice: Undefined variable: foo in /path/to/example.php on line 4

However, with xDebug installed, the error would read

Notice: Undefined variable: foo in /path/to/example.php on line 4

Call Stack:
    0.0003     635008   1. {main}() /path/to/example.php:0
    0.0004     635008   2. main() /path/to/example.php:6

The last item in the call stack

2. main() /path/to/example.php:6

is the function/method (main) where the error occurred, including a file (example.php) and line number (:6). The line number is where the function was called, not where the error occurred. You can find the line number where the error occurred in the original message

Notice: Undefined variable: foo in /path/to/example.php on line 4

In addition to this call stack, xDebug will wrap the displayed errors in a garish orange HTML rectangle to help draw a developer’s attention to them.

image of xdebug error display

Wrap Up

As you can see, with over 18 years of constant but backwards compatible development, PHP error’s handling features are broad, powerful, and incredibly inconsistant. While this article puts a dent in the topic, there’s a lot it doesn’t cover. (Have a favorite we didn’t mention? Drop it in a comment below or get in touch.

For day to day programming, you shouldn’t need to dive this deep into error handling. However, if you’re planning on making programming a long term part of your career, the ability to diagnose foreign systems is a must have skill. This means knowing every nook and cranny of what’s possible, even if it’s something you’d never do yourself.

Originally published September 15, 2013
Series NavigationMagento’s Mini Error Framework >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 15th September 2013

email hidden; JavaScript is required