The three nothings of PHP
Mathematicians have the concept of infinity, which represents something that cannot be topped. Strangely enough, infinities come in different sizes : some infinities are larger than others. That sounds quite paradoxical, yet somewhat understandable. And yet, there are no less than three nothings in PHP.
On the other hand, PHPians, if we can say that, have three kinds of nothing. The fun part is that they are incompatible one with another. Let’s review all them, and see how they behave. They are, in order of appearance : null, void and the uninitialized typed property.
To be clear, we use ‘nothing’ as a meta-concept, to cover all three of them : the absence of data. There is no keyword ‘nothing’ in PHP.
Null
Null
is probably the first keyword you yelled in your head, when reading the question of the introduction of this post. Of course, null
is the quintessential value that is nothing.
In fact, when an variable or a property is not defined, it is considered as null
by PHP.
<?php var_dump($undefined); // NULL ?>
This is possibly one of the root reason for misunderstanding the isset()
function. A variable is set when it hold something, some value; and a variable is not set, when it holds nothing, or no value. Since null
is nothing, a variable does not exist if it holds nothing.
(unset)
Historically, the old (unset)
operator used to do that : typecast any value to null
. The type null
contains only one value, and, of course, it is null
: that destroys the requested variable. Unsetting variables used to be equivalent to assign them nothing.
Nowadays, (unset)
is gone, and unsetting a variable is achieved with the unset()
function. I love the apparent shift of parenthesis in the syntax : (unset)
=> unset()
. Quite an upgrade.
Nothing is still something
The paradoxical nature of null
is that it is something that represents nothing. Yet, it is possible to add something to null
and get an integer; it is possible to compare null
to something else or to itself. It is also possible to assign nothing to a property, and make it unavailable.
In the example below, it is not possible to use isset()
to distinguish between a property $p
without a value, property $n
with a default value of null
and an undefined property $x
. In fact, all of them are represented by the null
.
<?php class x { public \$p; public \$n = null; } $a = new x; var_dump($a->b); $a->c = null; var_dump(isset($a->p)); // false var_dump(isset($a->n)); // false var_dump(isset($a->x)); // false ?>
Void
void
is the equivalent of null
, for types. It represents nothing
, as a type. It is used for return types of methods.
<?php function foo() : void { echo __FUNCTION__; } ?>
There is no void
type available for arguments or properties, since it would mean that such typed variable would always hold nothing, not even null
. So, void
-typed variables are simply omitted in the parameters list (same for properties). No need to declare them, so no need to type them either.
<?php // artist representation of a void parameter function foo(void $x) : void { echo <strong>FUNCTION</strong>; } foo(); ?>
null and void are not compatible
It is noteworthy that void
is not compatible with null
. In a void
method, one has to return without argument, or not return at all. Returning null
in a void
method yields an error.
<?php function foo() : void { return ; // valid return null; // invalid //Fatal error: A void function must not return a value (did you mean "return;" instead of "return null;"?) } ?>
Nothing can be returned, not even null. So null
is a nothing, and yet, it is something that is not void
.
null
and void
are now both types
Furthermore, the upcoming PHP 8.2 has a dedicated type Null
: this is useful for method that returns only null
, but not void
. Consistently, void
and null
are now different types.
Parameters and property may now also use the null
type. It forces the creation of a variable that consistently contains the null
value. It is as useless as the void
parameter, except for one case : to keep the signature of a method unchanged, yet deprecate a parameter by forcing it to null
. May be to forbid the usage of a property.
The case of array
and void
Finally, there is also the case for array(). Array() looks like a function, though it is actually a language construct. It builds PHP famous arrays.
array
may contains items, with or without a key. An array may hold a null
value : in that case, the array contains nothing, and yet it is not empty. An empty array holds something else, which might very well be the elusive void
value.
<?php print count([]); // 0 print count([null]); // 1 ?>
Empty arrays are usually processed as a separate case, whenever they are processed. For example, foreach() on an empty array simply ignore all looping and skips the instruction; array_sum() returns a default 0 on an empty array. With custom code, the empty arrays are usually checked with empty()
or count() === 0
, and when the array is not empty, then it is processed.
Uninitialized typed properties
The last case of nothing in PHP is related to typed properties. We have already seen that uninitialized properties are actually set to null
. It is the traditional process of PHP, anytime undefined values have to be handled.
And yet, typed properties behave differently.
<?php class x { public $p; public string $s; } $y = new x; var_dump($y->p); // NULL var_dump($y->s); // Fatal error: Uncaught Error: Typed property x::$s must not be accessed before initialization ?>
In the code above, the $p
property is NULL, as expected. On the other hand, $s
property is … not null
: it is a Fatal error. It is still nothing, since it has no default value, but it is also different from NULL
, as it is not possible to access it until it has been written. Until that moment, the property holds a special nothing that cannot be used.
Indeed, in the case the property needs to be checked before usage, and to avoid a Fatal error, the code must catch the potential error.
<?php class x { public string $p; function cache(string $v) { try { // test if $this->p is already set or not $this->p === null; // a call to isset() or empty() would not throw an error } catch(\Error $e) { // if not, set the cache $this->p = $v; } // return the cached value return $this->p; } } ?>
The nothing is eating us!
Bringing together all the notions of ‘nothing’ leads to intriguing questions. It starts with the paradox that null
is a value that represents no-value. By itself, it should not exists, and yet, null
is ubiquitous in PHP code. Many error situations are smoothed by returning null
and leaving the check to the calling context. There are even dedicated operators, such as ??
and ?->
, which turns those absence of values into default values.
With PHP 8.1, null values are getting less and less needed, as even object-typed properties may get a default value as an object. It entice us to rely less and less on null
and provide a valid and usable object. This is the NullPattern design pattern in action.
<?php class x { public A $p = new A(); } ?>