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 nullis 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 nulland 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 voidmethod, 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 nullare 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 nullvalue : 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();
  }
?>