Best way to test for a variable's existence in PHP; isset() is clearly broken

    |
  • Added:
  • |
  • In: Basic PHP

From the isset() docs:

isset() will return FALSE if testing a variable that has been set to NULL.

Basically, isset() doesn't check for whether the variable is set at all, but whether it's set to anything but NULL.

Given that, what's the best way to actually check for the existence of a variable? I tried something like:

if(isset($v) || @is_null($v))

(the @ is necessary to avoid the warning when $v is not set) but is_null() has a similar problem to isset(): it returns TRUE on unset variables! It also appears that:

@($v === NULL)

works exactly like @is_null($v), so that's out, too.

How are we supposed to reliably check for the existence of a variable in PHP?


Edit: there is clearly a difference in PHP between variables that are not set, and variables that are set to NULL:

<?php
$a = array('b' => NULL);
var_dump($a);

PHP shows that $a['b'] exists, and has a NULL value. If you add:

var_dump(isset($a['b']));
var_dump(isset($a['c']));

you can see the ambiguity I'm talking about with the isset() function. Here's the output of all three of these var_dump()s:

array(1) {
  ["b"]=>
  NULL
}
bool(false)
bool(false)

Further edit: two things.

One, a use case. An array being turned into the data of an SQL UPDATE statement, where the array's keys are the table's columns, and the array's values are the values to be applied to each column. Any of the table's columns can hold a NULL value, signified by passing a NULL value in the array. You need a way to differentiate between an array key not existing, and an array's value being set to NULL; that's the difference between not updating the column's value and updating the column's value to NULL.

Second, Zoredache's answer, array_key_exists() works correctly, for my above use case and for any global variables:

<?php
$a = NULL;
var_dump(array_key_exists('a', $GLOBALS));
var_dump(array_key_exists('b', $GLOBALS));

outputs:

bool(true)
bool(false)

Since that properly handles just about everywhere I can see there being any ambiguity between variables that don't exist and variables that are set to NULL, I'm calling array_key_exists() the official easiest way in PHP to truly check for the existence of a variable.

(Only other case I can think of is for class properties, for which there's property_exists(), which, according to its docs, works similarly to array_key_exists() in that it properly distinguishes between not being set and being set to NULL.)

This Question Has 17 Answeres | Orginal Question | chazomaticus

If the variable you are checking would be in the global scope you could do:

array_key_exists('v', $GLOBALS)

Object properties can be checked for existence by property_exists

Example from a unit test:

function testPropertiesExist()
{
    $sl =& $this->system_log;
    $props = array('log_id',
                   'type',
                   'message',
                   'username',
                   'ip_address',
                   'date_added');

    foreach($props as $prop) {
        $this->assertTrue(property_exists($sl, $prop),
                           "Property <{$prop}> exists");
    }
}

Sometimes I get a little lost trying to figure out which comparison operation to use in a given situation. isset() only applies to uninitialized or explicitly null values. Passing/assigning null is a great way to ensure a logical comparison works as expected.

Still, it's a little difficult to think about so here's a simple matrix comparing how different values will be evaluated by different operations:

|           | ===null | is_null | isset | empty | if/else | ternary | count>0 |
| -----     | -----   | -----   | ----- | ----- | -----   | -----   | -----   |
| $a;       | true    | true    |       | true  |         |         |         |
| null      | true    | true    |       | true  |         |         |         |
| []        |         |         | true  | true  |         |         |         |
| 0         |         |         | true  | true  |         |         | true    |
| ""        |         |         | true  | true  |         |         | true    |
| 1         |         |         | true  |       | true    | true    | true    |
| -1        |         |         | true  |       | true    | true    | true    |
| " "       |         |         | true  |       | true    | true    | true    |
| "str"     |         |         | true  |       | true    | true    | true    |
| [0,1]     |         |         | true  |       | true    | true    | true    |
| new Class |         |         | true  |       | true    | true    | true    |

To fit the table I compressed the labels a bit:

  • $a; refers to a declared but unassigned variable
  • everything else in the first column refers to an assigned value, like:
    • $a = null;
    • $a = [];
    • $a = 0;
  • the columns refer to comparison operations, like:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false

All results are boolean, true is printed and false is omitted.

You can run the tests yourself, check this gist:
https://gist.github.com/mfdj/8165967

You can use the compact language construct to test for the existence of a null variable. Variables that do not exist will not turn up in the result, while null values will show.

$x = null;
$y = 'y';

$r = compact('x', 'y', 'z');
print_r($r);

// Output:
// Array ( 
//  [x] => 
//  [y] => y 
// )

In the case of your example:

if (compact('v')) {
   // True if $v exists, even when null. 
   // False on var $v; without assignment and when $v does not exist.
}

Of course for variables in global scope you can also use array_key_exists().

B.t.w. personally I would avoid situations like the plague where there is a semantic difference between a variable not existing and the variable having a null value. PHP and most other languages just does not think there is.

I don't agree with your reasoning about NULL, and saying that you need to change your mindset about NULL is just weird.

I think isset() was not designed correctly, isset() should tell you if the variable has been set and it should not be concerned with the actual value of the variable.

What if you are checking values returned from a database and one of the columns have a NULL value, you still want to know if it exists even if the value is NULL...nope dont trust isset() here.

likewise

$a = array ('test' => 1, 'hello' => NULL);

var_dump(isset($a['test']));   // TRUE
var_dump(isset($a['foo']));    // FALSE
var_dump(isset($a['hello']));  // FALSE

isset() should have been designed to work like this:

if(isset($var) && $var===NULL){....

this way we leave it up to the programmer to check types and not leave it up to isset() to assume its not there because the value is NULL - its just stupid design

I think the only full solution is to report notices with

error_reporting(E_ALL); // Enables E_NOTICE

But you will have to fix all the notices generated by undefined variables, constants, array keys, class properties amongst others. Once you have done that you won't have to worry about the difference between null and not declared variables, and the ambiguity dissappears.

Enabling notice reporting might not be a good alternative in all situations, but there are good reasons to enable it:

Why should I fix E_NOTICE errors?

In my case was more than a year working in a proyect without it, but was used to be careful about declaring variables, so it was fast to transition.

Explaining NULL, logically thinking

I guess the obvious answer to all of this is... Don't initialise your variables as NULL, initalise them as something relevant to what they are intended to become.

Treat NULL properly

NULL should be treated as "non-existant value", which is the meaning of NULL. The variable can't be classed as existing to PHP because it hasn't been told what type of entity it is trying to be. It may aswell not exist, so PHP just says "Fine, it doesn't because there's no point to it anyway and NULL is my way of saying this".

An argument

Let's argue now. "But NULL is like saying 0 or FALSE or ''.

Wrong, 0-FALSE-'' are all still classed as empty values, but they ARE specified as some type of value or pre-determined answer to a question. FALSE is the answer to yes or no,'' is the answer to the title someone submitted, and 0 is the answer to quantity or time etc. They ARE set as some type of answer/result which makes them valid as being set.

NULL is just no answer what so ever, it doesn't tell us yes or no and it doesn't tell us the time and it doesn't tell us a blank string got submitted. That's the basic logic in understanding NULL.

Summary

It's not about creating wacky functions to get around the problem, it's just changing the way your brain looks at NULL. If it's NULL, assume it's not set as anything. If you are pre-defining variables then pre-define them as 0, FALSE or "" depending on the type of use you intend for them.

Feel free to quote this. It's off the top of my logical head :)

According to the PHP Manual for the empty() function, "Determine whether a variable is considered to be empty. A variable is considered empty IF IT DOES NOT EXIST or if its value equals FALSE. empty() does not generate a warning if the variable does not exist." (My emphasis.) That means the empty() function should qualify as the "best way to test a variable's existence in PHP", per the title Question.

However, this is not good enough, because the empty() function can be fooled by a variable that does exist and is set to NULL.

I'm interrupting my earlier answer to present something better, because it is less cumbersome than my original answer (which follows this interruption, for comparing).

function undef($dnc) //do not care what we receive
  { $inf=ob_get_contents();             //get the content of the buffer
    ob_end_clean();                     //stop buffering outputs, and empty the buffer
    if($inf>"")                         //if test associated with the call to this function had an output
    { if(false!==strpos($inf, "Undef"); //if the word "Undefined" was part of the output
        return true;                    //tested variable is undefined
    }
    return false;                       //tested variable is not undefined
  }

Two simple lines of code can use the above function to reveal if a variable is undefined:

ob_start();                           //pass all output messages (including errors) to a buffer
  if(undef($testvar===null))            //in this case the variable being tested is $testvar

You can follow those two lines with anything appropriate, such as this example:

echo("variable is undefined");
  else
    echo("variable exists, holding some value");

I wanted to put the call to ob_start() and the ($testvar===null) inside the function, and simply pass the variable to the function, but it doesn't work. Even if you try to use "pass by reference" of the variable to the function, the variable BECOMES defined, and then the function can never detect that it previously had been undefined. What is presented here is a compromise between what I wanted to do, and what actually works.

The preceding implies that there is another way to always avoid running into the "Undefined variable" error message. (The assumption here is, preventing such a message is why you want to test to see if a variable is undefined.)

function inst(&$v) { return; }  //receive any variable passed by reference; instantiates the undefined

Just call that function before doing something to your $testvar:

inst($testvar);                //The function doesn't affect any value of any already-existing variable

The newly-instantiated variable's value is set to null, of course!

(Interruption ends)

So, after some studying and experimenting, here is something guaranteed to work:

function myHndlr($en, $es, $ef, $el)
 { global $er;
   $er = (substr($es, 0, 18) == "Undefined variable");
   return;
 }

 $er = false;
 if(empty($testvar))
 { set_error_handler("myHndlr");
   ($testvar === null);
   restore_error_handler();
 }
 if($er)  // will be 1 (true) if the tested variable was not defined.
 { ; //do whatever you think is appropriate to the undefined variable
 }

The explanation: A variable $er is initialized to a default value of "no error". A "handler function" is defined. If the $testvar (the variable we want to know whether or not is undefined) passes the preliminary empty() function test, then we do the more thorough test. We call the set_error_handler() function to use the previously-defined handler function. Then we do a simple identity-comparison involving $testvar, WHICH IF UNDEFINED WILL TRIGGER AN ERROR. The handler function captures the error and specifically tests to see if the reason for the error is the fact that the variable is undefined. The result is placed in the error-information variable $er, which we can later test to do whatever we want as a result of knowing for sure whether or not $testvar was defined. Because we only need the handler function for this limited purpose, we restore the original error-handling function. The "myHndlr" function only needs to be declared once; the other code can be copied to whatever places are appropriate, for $testvar or any other variable we want to test this way.

As an addition to greatbigmassive's discussion of what NULL means, consider what "the existence of a variable" actually means.

In many languages, you have to explicitly declare every variable before you use it; this may determine its type, but more importantly it declares its scope. A variable "exists" everywhere in its scope, and nowhere outside it - be that a whole function, or a single "block".

Within its scope, a variable assigns some meaning to a label which you, the programmer, have chosen. Outside its scope, that label is meaningless (whether you use the same label in a different scope is basically irrelevant).

In PHP, variables do not need to be declared - they come to life as soon as you need them. When you write to a variable for the first time, PHP allocates an entry in memory for that variable. If you read from a variable that doesn't currently have an entry, PHP considers that variable to have the value NULL.

However, automatic code quality detectors will generally warn you if you use a variable without "initialising" it first. Firstly, this helps detect typos, such as assigning to $thingId but reading from $thing_id; but secondly, it forces you to consider the scope over which that variable has meaning, just as a declaration would.

Any code that cares whether a variable "exists" is part of the scope of that variable - whether or not it has been initialised, you as a programmer have given that label meaning at that point of the code. Since you're using it, it must in some sense "exist", and if it exists, it must have an implicit value; in PHP, that implicit value is null.

Because of the way PHP works, it is possible to write code that treats the namespace of existent variables not as a scope of labels you have given meaning to, but as some kind of key-value store. You can, for instance, run code like this: $var = $_GET['var_name']; $$var = $_GET['var_value'];. Just because you can, doesn't mean it's a good idea.

It turns out, PHP has a much better way of representing key-value stores, called associative arrays. And although the values of an array can be treated like variables, you can also perform operations on the array as a whole. If you have an associative array, you can test if it contains a key using array_key_exists().

You can also use objects in a similar way, dynamically setting properties, in which case you can use property_exists() in exactly the same way. Of course, if you define a class, you can declare which properties it has - you can even choose between public, private, and protected scope.

Although there is a technical difference between a variable (as opposed to an array key, or an object property) that hasn't been initialised (or that has been explicitly unset()) and one whose value is null, any code that considers that difference to be meaningful is using variables in a way they're not meant to be used.

Try using

unset($v)

It seems the only time a variable is not set is when it is specifically unset($v). It sounds like your meaning of 'existence' is different than PHP's definition. NULL is certainly existing, it is NULL.

I'm going to add a quick two cents to this. One reason this issue is confusing is because this scenario seems to return the same result with error reporting not on full:

$a = null;
var_dump($a); // NULL
var_dump($b); // NULL

You could assume from this result that the difference between $a = null and not defining $b at all is nothing.

Crank error reporting up:

NULL

Notice: Undefined variable: b in xxx on line n
NULL

Note: it threw an undefined variable error, but the output value of var_dump is still NULL.

PHP obviously does have an internal ability to distinguish between a null variable and an undefined variable. It seems to me that there should be a built in function to check for this.

I think the accepted answer is good for the most part, but if I was going to implement it I would write a wrapper for it. As previously mentioned in this answer, I have to agree that I haven't actually encountered a situation where this has been a problem. I seem to almost always end up in a scenario where my variables are either set and defined, or they aren't (undefined, unset, null, blank, etc). Not to say that a situation like this won't occur in future, but as it seems to be quite a unique issue I'm not surprised that the PHP devs haven't bothered to put this in.

I prefer using not empty as the best method to check for the existence of a variable that a) exists, and b) is not null.

if (!empty($variable)) do_something();

I have to say in all my years of PHP programming, I have never encountered a problem with isset() returning false on a null variable. OTOH, I have encountered problems with isset() failing on a null array entry - but array_key_exists() works correctly in that case.

For some comparison, Icon explicitly defines an unused variable as returning &null so you use the is-null test in Icon to also check for an unset variable. This does make things easier. On the other hand, Visual BASIC has multiple states for a variable that doesn't have a value (Null, Empty, Nothing, ...), and you often have to check for more than one of them. This is known to be a source of bugs.

isset checks if the variable is set and, if so, whether its value is not NULL. The latter part is (in my opinion) not within the scope of this function. There is no decent workaround to determine whether a variable is NULL because it is not set or because it is explicitly set to NULL.

Here is one possible solution:

$e1 = error_get_last();
$isNULL = is_null(@$x);
$e2 = error_get_last();
$isNOTSET = $e1 != $e2;
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

Other workaround is to probe the output of get_defined_vars():

$vars = get_defined_vars();
$isNOTSET = !array_key_exists("x", $vars);
$isNULL = $isNOTSET ? true : is_null($x);
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

Attempting to give an overview of the various discussions and answers:

There is no single answer to the question which can replace all the ways isset can be used. Some use cases are addressed by other functions, while others do not stand up to scrutiny, or have dubious value beyond code golf. Far from being "broken" or "inconsistent", other use cases demonstrate why isset's reaction to null is the logical behaviour.

Real use cases (with solutions)

1. Array keys

Arrays can be treated like collections of variables, with unset and isset treating them as though they were. However, since they can be iterated, counted, etc, a missing value is not the same as one whose value is null.

The answer in this case, is to use array_key_exists() instead of isset().

Since this is takes the array to check as a function argument, PHP will still raise "notices" if the array itself doesn't exist. In some cases, it can validly be argued that each dimension should have been initialised first, so the notice is doing its job. For other cases, a "recursive" array_key_exists function, which checked each dimension of the array in turn, would avoid this, but would basically be the same as @array_key_exists. It is also somewhat tangential to the handling of null values.

2. Object properties

In the traditional theory of "Object-Oriented Programming", encapsulation and polymorphism are key properties of objects; in a class-based OOP implementation like PHP's, the encapsulated properties are declared as part of the class definition, and given access levels (public, protected, or private).

However, PHP also allows you to dynamically add properties to an object, like you would keys to an array, and some people use class-less objects (technically, instances of the built in stdClass, which has no methods or private functionality) in a similar way to associative arrays. This leads to situations where a function may want to know if a particular property has been added to the object given to it.

As with array keys, a solution for checking object properties is included in the language, called, reasonably enough, property_exists.

Non-justifiable use cases, with discussion

3. register_globals, and other pollution of the global namespace

The register_globals feature added variables to the global scope whose names were determined by aspects of the HTTP request (GET and POST parameters, and cookies). This can lead to buggy and insecure code, which is why it has been disabled by default since PHP 4.2, released Aug 2000 and removed completely in PHP 5.4, released Mar 2012. However, it's possible that some systems are still running with this feature enabled or emulated. It's also possible to "pollute" the global namespace in other ways, using the global keyword, or $GLOBALS array.

Firstly, register_globals itself is unlikely to unexpectedly produce a null variable, since the GET, POST, and cookie values will always be strings (with '' still returning true from isset), and variables in the session should be entirely under the programmer's control.

Secondly, pollution of a variable with the value null is only an issue if this over-writes some previous initialization. "Over-writing" an uninitialized variable with null would only be problematic if code somewhere else was distinguishing between the two states, so on its own this possibility is an argument against making such a distinction.

4. get_defined_vars and compact

A few rarely-used functions in PHP, such as get_defined_vars and compact, allow you to treat variable names as though they were keys in an array. For global variables, the super-global array $GLOBALS allows similar access, and is more common. These methods of access will behave differently if a variable is not defined in the relevant scope.

Once you've decided to treat a set of variables as an array using one of these mechanisms, you can do all the same operations on it as on any normal array. Consequently, see 1.

Functionality that existed only to predict how these functions are about to behave (e.g. "will there be a key 'foo' in the array returned by get_defined_vars?") is superfluous, since you can simply run the function and find out with no ill effects.

4a. Variable variables ($$foo)

While not quite the same as functions which turn a set of variables into an associative array, most cases using "variable variables" ("assign to a variable named based on this other variable") can and should be changed to use an associative array instead.

A variable name, fundamentally, is the label given to a value by the programmer; if you're determining it at run-time, it's not really a label but a key in some key-value store. More practically, by not using an array, you are losing the ability to count, iterate, etc; it can also become impossible to have a variable "outside" the key-value store, since it might be over-written by $$foo.

Once changed to use an associative array, the code will be amenable to solution 1. Indirect object property access (e.g. $foo->$property_name) can be addressed with solution 2.

5. isset is so much easier to type than array_key_exists

I'm not sure this is really relevant, but yes, PHP's function names can be pretty long-winded and inconsistent sometimes. Apparently, pre-historic versions of PHP used a function name's length as a hash key, so Rasmus deliberately made up function names like htmlspecialchars so they would have an unusual number of characters...

Still, at least we're not writing Java, eh? ;)

6. Uninitialized variables have a type

The manual page on variable basics includes this statement:

Uninitialized variables have a default value of their type depending on the context in which they are used

I'm not sure whether there is some notion in the Zend Engine of "uninitialized but known type" or whether this is reading too much into the statement.

What is clear is that it makes no practical difference to their behaviour, since the behaviours described on that page for uninitialized variables are identical to the behaviour of a variable whose value is null. To pick one example, both $a and $b in this code will end up as the integer 42:

unset($a);
$a += 42;

$b = null;
$b += 42;

(The first will raise a notice about an undeclared variable, in an attempt to make you write better code, but it won't make any difference to how the code actually runs.)

99. Detecting if a function has run

(Keeping this one last, as it's much longer than the others. Maybe I'll edit it down later...)

Consider the following code:

$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
    if ( some_test($thing, $test_value) ) {
        $result = some_function($thing);
    }
}
if ( isset($result) ) {
    echo 'The test passed at least once!';
}

If some_function can return null, there's a possibility that the echo won't be reached even though some_test returned true. The programmer's intention was to detect when $result had never been set, but PHP does not allow them to do so.

However, there are other problems with this approach, which become clear if you add an outer loop:

foreach ( $list_of_tests as $test_value ) {
    // something's missing here...
    foreach ( $list_of_things as $thing ) {
        if ( some_test($thing, $test_value) ) {
            $result = some_function($thing);
        }
    }
    if ( isset($result) ) {
        echo 'The test passed at least once!';
    }
}

Because $result is never initialized explicitly, it will take on a value when the very first test passes, making it impossible to tell whether subsequent tests passed or not. This is actually an extremely common bug when variables aren't initialised properly.

To fix this, we need to do something on the line where I've commented that something's missing. The most obvious solution is to set $result to a "terminal value" that some_function can never return; if this is null, then the rest of the code will work fine. If there is no natural candidate for a terminal value because some_function has an extremely unpredictable return type (which is probably a bad sign in itself), then an additional boolean value, e.g. $found, could be used instead.

Thought experiment one: the very_null constant

PHP could theoretically provide a special constant - as well as null - for use as a terminal value here; presumably, it would be illegal to return this from a function, or it would be coerced to null, and the same would probably apply to passing it in as a function argument. That would make this very specific case slightly simpler, but as soon as you decided to re-factor the code - for instance, to put the inner loop into a separate function - it would become useless. If the constant could be passed between functions, you could not guarantee that some_function would not return it, so it would no longer be useful as a universal terminal value.

The argument for detecting uninitialised variables in this case boils down to the argument for that special constant: if you replace the comment with unset($result), and treat that differently from $result = null, you are introducing a "value" for $result that cannot be passed around, and can only be detected by specific built-in functions.

Thought experiment two: assignment counter

Another way of thinking about what the last if is asking is "has anything made an assignment to $result?" Rather than considering it to be a special value of $result, you could maybe think of this as "metadata" about the variable, a bit like Perl's "variable tainting". So rather than isset you might call it has_been_assigned_to, and rather than unset, reset_assignment_state.

But if so, why stop at a boolean? What if you want to know how many times the test passed; you could simply extend your metadata to an integer and have get_assignment_count and reset_assignment_count...

Obviously, adding such a feature would have a trade-off in complexity and performance of the language, so it would need to be carefully weighed against its expected usefulness. As with a very_null constant, it would be useful only in very narrow circumstances, and would be similarly resistant to re-factoring.

The hopefully-obvious question is why the PHP runtime engine should assume in advance that you want to keep track of such things, rather than leaving you to do it explicitly, using normal code.

If I run the following:

echo '<?php echo $foo; ?>' | php

I get an error:

PHP Notice:  Undefined variable: foo in /home/altern8/- on line 1

If I run the following:

echo '<?php if ( isset($foo) ) { echo $foo; } ?>' | php

I do not get the error.

If I have a variable that should be set, I usually do something like the following.

$foo = isset($foo) ? $foo : null;

or

if ( ! isset($foo) ) $foo = null;

That way, later in the script, I can safely use $foo and know that it "is set", and that it defaults to null. Later I can if ( is_null($foo) ) { /* ... */ } if I need to and know for certain that the variable exists, even if it is null.

The full isset documentation reads a little more than just what was initially pasted. Yes, it returns false for a variable that was previously set but is now null, but it also returns false if a variable has not yet been set (ever) and for any variable that has been marked as unset. It also notes that the NULL byte ("\0") is not considered null and will return true.

Determine whether a variable is set.

If a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte ("\0") is not equivalent to the PHP NULL constant.

THE only way to know if a variable is defined in current scope ($GLOBALS is not trustworthy) is the following:

array_key_exists('var_name',get_defined_vars())


Search
Meet with owner

Sajjad Hossain

Hey, I am Sajjad, working in web development sector since 2012. I love to do amazing things. Let's do a project together.
Connect Social With PHPAns
Top