PHP isset() and undefined vs. NULL variables

[ PHP logo ]

As is often the case, while trying to overcome one limitation I encountered with the PHP programming language, I managed to overcome another one that often leaves programmers stumped; namely, the apparent inability to distinguish between variables that aren’t defined and variables that are defined as NULL.

PHP is a run-time-interpreted language that uses late binding, and it’s possible to execute code that contains variables that have never been defined. But PHP’s variables can also be defined with a NULL value, and an undefined variable is different from a variable that has been defined with a NULL value.

The PHP language has a built-in function, isset, that indicates if a variable has a non-NULL value, but there is no function that distinguishes between an undefined variable and one that is set to NULL. In this article I start with some basics for background, and then present a solution.

Undefined, NULL, empty, and non-empty values

In the code fragment below, I define $null, initialized to the value NULL, $empty, initialized to an empty string, and $true, initialized to the value TRUE. The variable $undefined is not defined at all, and to be doubly sure, I pass it to unset, which destroys any definition that it might have had.

$null=NULL; // this variable is defined as NULL $empty=""; // defined, but "empty" $true=TRUE; // defined, not NULL & notempty unset($undefined); // this variable is not defined

Here’s one way of viewing the relationship between these variables and their defined content, if any:

$null $empty $true $undefined

The blue cells show defined content. The variable $undefined has no content.

The isset, empty, and is_null functions

There are three standard PHP functions that revolve around this issue, isset, empty, and is_null, but none of them are actually able to tackle it. Instead, they all act in similar, but slightly different ways. An example is the best way to explain what these functions do, so let’s look specifically at what these functions return when we pass them the variables I defined above.

// dumpbool - print a label w/ a Boolean function dumpbool($label, $expr) { print "$label: " . ($expr ? "TRUE" : "FALSE") . "\n"; }

To simplify the code examples, I’ll use a function I created, dumpbool, shown on the right.

dumpbool('isset($undefined)',isset($undefined)); dumpbool('empty($undefined)',empty($undefined)); dumpbool('is_null($undefined)',is_null($undefined)); print "\n"; dumpbool('isset($null)',isset($null)); dumpbool('empty($null)',empty($null)); dumpbool('is_null($null)',is_null($null)); print "\n"; dumpbool('isset($empty)',isset($empty)); dumpbool('empty($empty)',empty($empty)); dumpbool('is_null($empty)',is_null($empty)); print "\n"; dumpbool('isset($true)',isset($true)); dumpbool('empty($true)',empty($true)); dumpbool('is_null($true)',is_null($true));

Here’s the output from the above code:

isset($undefined): FALSE empty($undefined): TRUE is_null($undefined): TRUE isset($null): FALSE empty($null): TRUE is_null($null): TRUE isset($empty): TRUE empty($empty): TRUE is_null($empty): FALSE isset($true): TRUE empty($true): FALSE is_null($true): FALSE

The above output shows that, using these functions, a variable defined as NULL is indistinguishable from a variable that has not been defined. But we’re looking only on the surface. Let’s look a little deeper.

PHP error handling

PHP reports certains types of notices, warnings, and errors about code execution with its “error handling and logging” functionality (which is different from the “exceptions” functionality introduced in PHP 5). It can be configured to ignore or report and/or log these errors and, by default, it ignores E_NOTICE errors. An application can change this setting at runtime by calling error_reporting or an application can call set_error_handler to tell PHP to invoke an application callback function when certain types of errors occur.

Referencing undefined variables

If you attempt to reference the (non-existent) value from an undefined variable, PHP produces the NULL value in its place and, if the system is so configured, generates an E_NOTICE error event. But the practice of using undefined PHP variables is so common, that systems often don’t change the default setting, and E_NOTICE error events usually pass unnoticed.

For example, consider two variables, $undefined and $undefined2, both initially undefined:

undef($undefined); undef($undefined2);

This table illustrates their initial values:

$undefined $undefined2

Let’s now (attempt) to assign the “value” of $undefined to $undefined2:

$undefined2 = $undefined;

During this statement’s execution, if the system was so configured, it generated the E_NOTICE error event. In any case, after this statement, the variable $undefined remains undefined and $undefined2 has the value NULL, as illustrated in this table:

$undefined $undefined2

Some PHP statements and functions that appear to reference a variable don’t actually reference it in the normal way. When invoking the isset and empty functions, for example, the E_NOTICE event is never triggered. But passing an undefined variable to is_null (or almost any other function) does trigger this event.

IMPORTANT NOTE: Use of the unary ampersand operator (e.g. &$var) on an undefined variable causes PHP to trigger an E_NOTICE error event (if so configured) and to define the variable as NULL. This also applies to pass-by-reference function arguments.

One solution

So, one solution is to determine if a variable is undefined or not by referencing the variable and detecting if the reference caused an E_NOTICE error event. The code below (included as an attachment at the bottom of this article) provides three functions, isdef_begin, isdef, and isdef_end. (The other functions below with names that start with an underscore help support the three interface functions.)

// Be silent about E_NOTICE errors, but make a note if we see one function _isdef_error_handler( $errno, $errstr, $errfile, $errline, $errcontext ) { $GLOBALS['_isdef_error_detected'] = TRUE; return TRUE; } // enable the error handler function isdef_begin() { if (empty($GLOBALS['_isdef_error_handler_enabled'])) { $GLOBALS['_isdef_error_handler_enabled'] = TRUE; set_error_handler("_isdef_error_handler",E_NOTICE); } _isdef_reset(); } // reset the error handler function _isdef_reset() { unset($GLOBALS['_isdef_error_detected']); } // disable the error handler function isdef_end() { if (!empty($GLOBALS['_isdef_error_handler_enabled'])) { restore_error_handler(); unset($GLOBALS['_isdef_error_handler_enabled']); } } // see if the variable is defined by seeing if an error was detected function isdef($var) { if (!empty($GLOBALS['_isdef_error_detected'])) { _isdef_reset(); return FALSE; } return TRUE; }

We only need to call isdef for variables for which isset returns FALSE, so here’s an example of how the above code can be invoked with $undefined and $null:

isdef_begin(); $is_defined = isdef($undefined); isdef_end(); dumpbool('isdef($undefined)',$is_defined); isdef_begin(); $is_defined = isdef($null); isdef_end(); dumpbool('isdef($null)',$is_defined);

Notice how isdef_begin and isdef_end bracket the call to isdef. Because of the way the code disables part of the PHP error-handling mechanism, it’s best to minimize the amount of time within that bracket and to call no other functions. It would be fine to make multiple calls to isdef, provided the other code within the bracket is as simple as possible and virtually guaranteed to not generate an error.

Here’s the output:

isdef($undefined): FALSE isdef($null): TRUE

Only $undefined is identified as being undefined.

Because a PHP function always receives defined values for its arguments, it is impossible to encapsulate this code inside a single function. In fact, the isdef function doesn’t directly reference the parameter passed to it, and presumes that any E_NOTICE error it detects was generated when the parameter was referenced by the function call. The error could have been generated by some other means, as demonstrated by the following code that produces the same output:

isdef_begin(); $dummy = $undefined; $is_defined = isdef(0); isdef_end(); dumpbool('isdef($undefined)',$is_defined); isdef_begin(); $dummy = $null; $is_defined = isdef(0); isdef_end(); dumpbool('isdef($null)',$is_defined);

A reference to an undefined variable in the (attempted) assignment to $dummy triggers an E_NOTICE error event, allowing the above code to work.

An academic exercise

As it turns out, if you really need to know if a variable is defined or not, the isdef function is probably not the best solution. As you can’t pass an undefined value to a function anyway, any code that would test for definition knows the name of the variable and, given the name of a variable, you can more simply test like this to see if it’s defined:

$isdef_var = isset($var) || array_key_exists('var',get_defined_vars()));

This code produces the same output as our code above that calls isdef:

dumpbool("array_key_exists('undefined',get_defined_vars())", array_key_exists('undefined',get_defined_vars())); dumpbool("array_key_exists('null',get_defined_vars())", array_key_exists('null',get_defined_vars()));

On benchmarks I ran, this method takes about 20% less processor time, unless you iterate on isdef many times, bracketed between isdef_begin and isdef_end, a very unlikely scenario. In any case, even that difference only matters if you’re spending a lot of time testing for undefined variables, another unlikely scenario.

No solution for function parameters

Because a function can never receive an undefined actual parameter, it is impossible to distinguish between a parameter that was passed as an undefined variable and a parameter that is NULL. My original goal was to see if it was possible, but this exercise proved to me that that quest leads to a dead end.

Daniel Norton is a computer nerd and cyclist in Austin, Texas.
[file] isdef.php.txt07/08/09 12:16 pm2.2 KB