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

17

196

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.)

Brythonic answered 6/1, 2009 at 20:46 Comment(13)
You can't check - but why do you need to?Bogus
Yes, a good example for why you really need this would be helpful.Ticino
I agree. As far as you need to know, neither are set because they are both null. You can't use them for anything.Torbart
NULL has a very specific meaning in PHP, and it's an entirely separate concept from whether a variable is set or not.Brythonic
Interesting observation, I wasn't aware of this. An answer has been found so this is redundant and incorrect - but I'd of probably used a placeholder value or an object for each row to make finding an unset property clearer.Judenberg
There ARE reasons to differentiate between null and nonexistent. For example, you're building up an object to represent a row in a database table. For every column in the row, you create a private variable, accessible only via the object's getter method. Suppose a column value is null. Now how does that getter method know whether there's no such column in the table, or whether this object just has a null value there? Fortunately, in my case, the private variable is actually an entry in a private array, so I can use array_key_exists, but this is a real problem.Jarret
The very same issue here for the same usage (db nulls). Having to use array_key_exists() is unintuitive. One more drop to my PHP cup...Predatory
I see no use cases in the edited question, or any of the answers and their comments, which actually justify having a variable which might or might not exist at runtime. Array keys and object properties can be treated as variables, and tested with isset, but their existence can also be verified with array_key_exists() and property_exists(), so use cases involving them should be considered separately.Tu
Especially in legacy setups, register_globals leads to global variables that might or might not exist at runtime.Brythonic
@Brythonic Firstly, if they're NULL, it still doesn't matter. Secondly, if it does, we can chalk that up to one of the many things which were wrong with that feature, which has been removed from PHP completely. Justifying one bad idea with another doesn't convince me of a sensible use for non-existent variables.Tu
It's been removed from new versions of PHP, yes. Unfortunately, it's not gone from every deployment of PHP. Also, it seems like a pointless semantic detail to quibble about whether we're talking about array elements or variables. Regardless of what standards you think code should adhere to, it's useful to know how to work around an inconsistency in the PHP language.Brythonic
@Brythonic But variables and array elements are fundamentally different things; just because you can do some of the same things with them doesn't mean they are or should be 100% interchangeable. There is no "inconsistency in the PHP language" here, just something you don't happen to like/understand. As for register_globals, I'm still struggling to think of a situation where even that would require such a distinction, since anything registered from the HTTP request would always be a string, not null.Tu
The real problem here is that PHP's database APIs treat database-NULL equivalently to PHP-NULL despite their being different things. (PHP-NULL is "uninitialized"; database-NULL is "initialized to empty")Kibe
T
100

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

array_key_exists('v', $GLOBALS) 
Ticino answered 6/1, 2009 at 21:7 Comment(7)
Ah ha! NOW you're talkin'! How would you do that for, say, class properties?Brythonic
As a variation, if the check needs to work for local scope variables as well, on can do a $defined_vars = get_defined_vars(); and then test via array_key_exists('v', $defined_vars);.Sitzmark
This looks a bit ugly to me, but in the case where you're actually checking an array element, it makes much more sense: isset($foo[$bar]) becomes array_key_exists($bar, $foo)Stamm
property_exists seems promising, except for this: > The property_exists() function cannot detect properties that are magically accessible using the __get magic method.Chondriosome
@Chondriosome Variables "created" via __get indeed don't exist. __get is arbitrary code used as a fallback for nonexistent variables, that can return whatever it wants regardless of whether any relevant data was ever stored.Ashleeashleigh
If you have a magic __get method, you will probably want to have a magic __isset method as well, which you could use.Bicarbonate
This is not probably relevant in reality, but ... get_defined_vars() + array_key_exists WILL NOT test if variable was really declared or not, it's just a way how to detect that variable was "initialized" with null... Just a simple declaration - $a; is a valid code, though this method would result in false :) But as I said, this is more of a "fun fact", that may concern a few random C people :-)Fuddyduddy
T
48

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.

Tu answered 5/9, 2013 at 21:52 Comment(3)
Regarding classes and properties, sadly property_exists() doesn't work when the property is array, for example: Class{ public $property = array() }. Throws an error.Camala
@Camala Seems to work fine for me: 3v4l.org/TnAY5 Care to provide a complete example?Tu
yeah it seems to work fine, something wrong was with my setup. Sorry for the false alarm :)Camala
S
23

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

Stereochemistry answered 29/12, 2013 at 7:5 Comment(1)
Maybe out of the scope of this question, but you might want to add "0" to the table, for completeness and clarity of the empty operationSyce
U
16

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.

Unbelieving answered 28/12, 2009 at 11:57 Comment(9)
PHP doesn't, but I wouldn't say most other languages don't. Most any language that declares variables will throw an error if a variable hasn't been declared, but you can set them to NULL. Semantically, NULL should mean "no resource," but not defining a variable is a programmer error.Emarie
@MMiller Sure, but writing code that follows one path in the case of "no resource" and a different path in the case of "programmer error" is fairly nonsensical. If you want to detect undeclared variables during debugging, use a static analysis tool, like you would to find potential errors in any language.Tu
@MMiller, Cool, how did you even thought of this.Spark
I end up looking at it a little differently now that I use JavaScript because there are more states. If you say alert(x); when x was never declared, it throws a reference error. If you do var x; alert(x); you get undefined. undefined is a different state from null. For example, many programs use an options hash, e.g. the options for a lightbox script might be: var options = { onClose: function () { ... }, centerHorizontally: true }. If onClose were set to null, that might mean do nothing, whereas undefined (or absent) implies use the default setting.Emarie
@MMiller As with so many examples here, your JS options example is of a key in some particular object/hash, not a bare variable. Thus the equivalent code in PHP would use array_key_exists or property_exists, not isset.Tu
@Tu Yes, I know. This comment was simply in response to the answer to point out that there is a purpose to having a case when something doesn't exist versus when it is null. I didn't intend my comment to be a response to the OP's question, but rather a rebuttal against this particular answer.Emarie
@MMiller But it doesn't work as a rebuttal, because the statement in the answer is explicitly about "a variable not existing", and your counter-example is about an object property / hash key not existing. The distinction between these cases is not just incidental.Tu
Yup, you're right, my mistake. Although the same would still apply if I did var onClose = options.onClose; if (onClose === undefined) { ... } else if (onClose === null) { ... }Emarie
@MMiller - indeed that is a better example. Still, after 20+ years programming in strict languages, the situations where I needed a distinctions between undefined and null are so rare that I don't miss it. IMHO, the main use for undefined is "programmer error in a non-strict language". In a strict language, if I need a distinct state for client did not state a value, then I declare a value appropriate to the situation, and test for it. Worst case, have to add a separate flag variable. But doing that rarely is better than having to ALWAYS cope with TWO different non-value states!!Labium
O
16

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 :)

Overstay answered 31/3, 2010 at 10:13 Comment(22)
Great answer. So many times I see people ranting about how they hate this or that feature of a language. But they seem to be assuming that "if it doesn't do it MY way, then it's broken." Yes, there are bad design decisions. But there are also very close-minded developers!Anuska
There IS a HUGE difference between unset variable and variable===null. One doesn't exist, the other has value null. Arguments that null means no value simply aren't true. Null IS A VALUE of type null. It's perfectly valid value and there's no reason for php to treat it as non-existing value, which it sadly does. It would be OK, if non-existing variables were null and every existing variable was not-null and assigning null into variable would unset it. But there are MANY situations, where functions return null as actual value. Then we're screwed up, because there's no bloody way to test for it.Thermionics
@Thermionics If a function returns NULL, and you're testing that return value, then you've put it somewhere, so you know that the container exists. Other than debugging, there should never be a case where a variable - as opposed to an array key or object property - "might or might not have been set yet".Tu
@Tu what brings you to that idea? Didn't you ever run into a situation, where a function is in the middle of complex loops and conditions and it might or might not have been run, and it's return value was/wasn't assigned to a variable, and testing on variable existence is the only thing to help you... Well, not really, since you can't test for set/not-set variable in php, you had to add that $my_function_has_run = true; right next to your function call. Well, that doesn't make the language less turing-complete. But it surely does make it more anoying.Thermionics
@Thermionics That sounds a lot like debugging to me. If that check is necessary in the final code, you've got some pretty wacky logic. Remember that in many languages, a variable either exists or not based on declaration in a particular scope, so such a test would be meaningless.Tu
@Tu true, but php doesn't follow logic of other languages. It has its own world, and in it's own world, variable is created when it's assigned to, there's no declaration for it. Besides, even in other languages, you can test variable existence. I'm not saying null being equivalent to unset variable is somehow big problem, it's just a small anoyance in a language, that has been built purely from small anoyances. So, whatever. Let's get back to business.Thermionics
@Thermionics See my answer for the reason an uninitialized variable could still be considered to "exist". You can even assign to another variable from it, and you get null. It would be far more annoying to have a second constant representing not-null-but-read-from-an-unitialized-variable, which would be the consistent result of isset() making such a distinction.Tu
I know we're "not supposed" to check variable existence in php, hell, there's not even any real way to check it. I'm not going to write code which deppends on it, because it's not possible in php. That's a limitation of php. There clearly is a difference between non-set and null variable, yet php provides no ways to distinguish those. Yet lot of meta functionality depends on it internaly: reading non-existing var produces notice, isset($a['x']) will tell you false if x is null, yet it will show up in count($a).. compact will work on all set variables, including nulls, and so on.Thermionics
@Thermionics Careful, once again some of your examples are using arrays, for which we have array_key_exists. The vast majority of functionality treats an uninitialized variable as equal to null, so it's perfectly reasonable not to distinguish between the two states. The notice is a hint that you should change your code to initialize it explicitly for better code, not because it makes any difference in itself. As for compact, that's more like code reflection than anything you'd see in every day code.Tu
This answer is flawed in one major way: in OO programming, null is the logical choice to mean "no object". For example, in unexceptional circumstances when a function can return an object or no object, null is the obvious choice. Technically in PHP, false or any other value considered false in boolean context could be used, but then you're losing some semantic purity. Thus, null is a perfectly reasonable value to initialize a variable to that should hold an object eventually, because it is relevant to what it's intended to become.Brythonic
IMSoP, the PHP manual actually makes it clear that uninitialized variables can have any number of types, depending on the context, so it's not correct to say "the vast majority of functionality..." without knowing what the code is doing. Also, nobody's arguing that you shouldn't initialize variables before using them. The question is simply wondering, given a situation where there's some ambiguity about whether a variable was declared, how can that be resolved.Brythonic
@Tu key is 3 characters long, isset has 5 characters, array_key_exists has 16. Think about it. Plus, array_key_exists complains if the array itself does not exist. Therefore checking index that's at the bottom of huge tree structure the proper way is going to introduce massive amount of boilerplate, all just for checking one key existence. Again, i'm not saying it's unusable, it's just introducing unnecessary pain. But I don't care, I'm running away from php anyway.Thermionics
@Brythonic 1) I have never said that there aren't valid uses for null; I have said that there aren't valid uses for "uninitialized" as a value/state distinct from null. 2) I hadn't seen that description of uninitialized variables before, but note that in practice they consistently behave the same as a null value being coerced in the same situation. I stand by the claim that except in very esoteric situations, there is not and should not be a meaningful distinction between the two states.Tu
@Thermionics I'm not really sure what the length of function names has to do with anything being discussed here. The argument for a recursive array_key_exists (i.e. one that performs multiple existence checks rather than checking a normal variable against a particular test) is an interesting one; I guess you can use the @ "shut up operator", but that does always seem like a hack. It's still not a use case for a function that detects uninitialized bare variables, rather than array keys, though.Tu
IMSoP, I think you're confused. My comment about null was in response to this answer, not you. And I'll repeat myself in regard to your claim: nobody's denying that! This conversation has gone far beyond being useful.Brythonic
@Brythonic Apologies, I did misunderstand that point. It is a valid clarification of the answer that null might be a valid initialization state. Of course, that would then be a constant in the code, so there would be no need to check if it had happened or not at runtime.Tu
NULL is perfectly valid for setting artibrary variables, if the value has yet to be determined that is. Argument for not setting a $var as NULL is thus broken. So is ISSET()Balenciaga
This answer is fundamentally flawed as demonstrated by enrey and others.Domash
As long as PHP throws errors for undefined variables, but not for null, then there is a difference. If null and undefined were really the same concept, then PHP should assume default undefined/undeclared variables to null and never throw an error, but no one wants that because it's a development nightmare. Null and undefined may not really be different in the context of value semantics, but they are very different when it comes to writing clear and debuggable code.Tillett
@ChrisMiddleton Exactly! In most languages, there's a big difference between null/nil and undefined. The former is a data type, meaning you can assign it to variables (it basically means "empty, but not an empty string or zero or false"). The latter is a read-only keyword that evaluates to true in the case of an identifier that isn't found. The difference is why JavaScript has two different keywords, and why stricter languages like Basic don't let you set Integers/Booleans/Strings to Null (only Variants or Objects).Lacewing
There are plenty of reasons to have variables set to null. What if I'm getting data from a database and I want to see if the column exists but is simply null (which happens all the time)?Bicarbonate
@HugoZink - IMHO you've misunderstood the discussion. No one suggested that variables should not be set to null. The only question is whether/when there need to be TWO different concepts: null vs. has never been set to anything, not even to null. This lengthy answer intends to demonstrate that when you need to make that distinction, there is some way to do so in php [except in limited cases, that there is disagreement about how important those are]. Though the code needed is different, depending on what entity you are examining.Labium
M
9

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");
    }
}
Marder answered 24/1, 2009 at 12:35 Comment(0)
T
6

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.

Tu answered 3/9, 2013 at 21:11 Comment(4)
Very good points, though not exactly an answer to the question.Brythonic
To the explicit question "How are we supposed to reliably check for the existence of a variable in PHP?" my answer is "you're not, and here's why". Both this answer and greatbigmassive's also answer the implicit question "why does isset() behave that way?".Tu
"If you read from a variable that doesn't currently have an entry, PHP considers that variable to have the value NULL." This is false. An undefined variable is simply undefined. It may return null when you try to access it, but that's irrelevant.Bicarbonate
@HugoZink Irrelevant to what? Any test you do of the value of an undefined variable will tell you that the value is null. Whether that value exists before you look at it is a question for philosophers, but as far as any observable behaviour is concerned the value is consistently null.Tu
B
4

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
Bebebebeerine answered 31/1, 2013 at 15:32 Comment(0)
P
2

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.

Pu answered 22/5, 2014 at 21:52 Comment(1)
The warning on undefined variables is a hint to the programmer that they have done something wrong in the code. Outside debugging (for which there are tools outside the language), there should never be a need for a program to detect such a state, because the programmer should always know what variables they are declaring.Tu
C
1

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.

Childbed answered 6/1, 2009 at 21:0 Comment(3)
He got the docs off that link. Read the first sentence, second paragraph of the description section on the link you provided It is exactly what he quoted above.Ticino
This isn't a bad practice for simple scripts, but in complex (e.g. large OO) projects, it becomes infeasible. Also, as I said above, is_null() returns TRUE for variables that are not set, so there's really no reason to do what you're saying except to avoid a PHP warning.Brythonic
For well designed "large OO" projects, why would this be a problem at all? Why would you ever have $foo in a method body that may have not been set before its first usage?Childbed
T
1

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.

Thorbert answered 6/1, 2009 at 21:50 Comment(0)
P
1

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.

Pyrognostics answered 6/1, 2009 at 22:17 Comment(2)
I'm not sure what you mean. If you have an array, with one element 'a', you don't have to unset() element 'b' for element 'b' not to exist in PHP, it just doesn't exist. Same thing with e.g. global variables, which you can think of as elements of the $GLOBALS array.Brythonic
But I agree that a variable with a NULL value does in fact exist.Brythonic
M
1

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

Millur answered 28/1, 2011 at 15:48 Comment(4)
Your example is not checking the existence of a variable, but of an array key. A solution to that exists, in the form of array_key_exists. You should never be in a situation where you don't know at runtime if an actual variable exists.Tu
@Brythonic Well, you should never be in a situation where register_globals is turned on, so I stand by that statement.Tu
Oh, I agree. Still, not everyone can control where their code is deployed. It's useful to have information for every situation, whether it's how things "should" be or not.Brythonic
@Brythonic If your problem is register_globals, then your answer is not a change to isset(). The PHP manual mentions "it really is generally a good programming practice to initialize variables first", which solves register_globals at design-time rather than run-time. There is also an FAQ entry giving an unregister_globals() function to deal with it at run-time.Tu
S
1

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.

Sandpiper answered 29/5, 2015 at 17:50 Comment(0)
G
0

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.

Garrotte answered 22/5, 2014 at 6:10 Comment(2)
If the intention is to avoid a warning that your variables haven't been declared, then the solution is to fix your code to declare them properly. Your inst function is basically like the @ error-suppression operator: "I know I'm doing something wrong here, but I just want that message to go away, without actually changing the operation of my code in any way".Tu
The detection methods, on the other hand, are ingenious, but I am still of the firm belief that you should never have any use for them other than to echo the very warning messages that they're catching. (You should probably clarify that your output buffering version requires error_reporting set high and display_errors to be turned on.)Tu
E
0

THE only way to know if a variable is defined in current scope ($GLOBALS is not trustworthy) is array_key_exists( 'var_name', get_defined_vars() ).

Erector answered 1/2, 2017 at 14:34 Comment(1)
I think that's what many other people said before, or am I wrong?Rockribbed
L
-1

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();
Laylalayman answered 7/3, 2011 at 22:28 Comment(1)
empty() does not check if the variable is null, it checks if it's false-y, e.g. not one of the "" (an empty string), 0 (0 as an integer), 0.0 (0 as a float), "0" (0 as a string), NULL, FALSE, array() (an empty array) and $var; (a variable declared, but without a value). Say you have a required radio field in a form with two inputs with the values 0 and 1. If you use empty() for validation and the user selects the 0 one, you'd inadvertently error out "required field cannot be empty". See the manual php.net/manual/en/function.empty.phpPredatory

© 2022 - 2024 — McMap. All rights reserved.