PHP: The case for standalone null parameters

PHP 8.0 introduced null and false as members of union types but disallowed them as standalone parameter types. PHP 8.2 changed that and allowed null as standalone parameter types. What is this good for? Should they extend this to “never” one day? Why do I call standalone null parameters poor man’s generics?

What’s a null parameter? What’s a nullable parameter?

A null parameter is a parameter of type null which can only ever have one value null. Its core meaning is “not a value” as opposed to “empty string” or “false” or “zero”.

A union type which has other values but can also contain null is called nullable. For example, boolean is not a nullable type. It has the possible values true and false. If we want to allow a third state, we can create a new nullable type bool|null which has the possible values true, false and null.
In modern php, bool is a union type of true, which can only have the value true, and false, which can only have the value false. So bool|null is equivalent to true|false|null. Union types including null can also be written with a preceding question mark: bool|null is equivalent to ?bool

Isn’t this a bit pointless?

A null parameter by itself is not very interesting. After all, we know its only possible value. It is valuable as a type which can be extended. According to Liskov substitution principle parameters of subtypes should be contravariant to parent types. If the parent type accepts null as a parameter, the child type must accept null as a parameter but may accept anything else. The opposite is true for return types. The child class may have a more specific return type than the parent class but must not return anything the parent would not allow. This is called covariance. In PHP, the top type is called mixed and allows basically everything, even null values. The null type is at the other end of the scale. If the parent returns null, the child must not return anything else. There is one more restricted return type, never. A function of type never must not return at all. It is either an infinite loop or may only exit the whole program. But never is not an allowed parameter type. There is also a return type void which is in between the two. Void may return from functions, but it returns semantically nothing. Not even null.

What is it good for?

Defining an interface parameter as null is allows to define an almost generic interface which might be substantiated in derived classes. Let’s look at an example.

<?php

interface A {

        public function get(null $value): mixed;
}

class B implements A
{
        public function get(null $value): ?object;
        {
                return $value
        }
}

class C extends B
{
        public function get(A|null $value): XmlElement;
        {
            if (is_null($value)
            {
                $value = new SimpleXmlElement("Not Null");
            }

                return $value;
        }
}

class D implements A
{
    public function get(B $value)
    {

    }
}

If you don’t see the point, let me explain. interface A defines that any implementing class must have a method get(null $value) which has only one mandatory parameter. This parameter must accept null as a value. Any additional parameters must be optional. Any derived class may make the first parameter accept more types and even make it optional. The only drawback: Class D cannot be implemented because function get does not accept null as its first parameter $value.

Generics: I wish I had a little more nothing

This is as close to actual generics as one gets with PHP. Generics are code templates which do not have specific parameter and return types until an actual class is created from them. Some other languages do have them but PHP doesn’t. Larry Garfield and others have made the case for them over and over again.

There are some challenges in the engine which make them hard to implement. It would matter less if we had some tbd or changeme type which can only exist in interfaces and can be overridden by any other type. But we don’t. At least we have standalone null.

Leave a Reply

Your email address will not be published. Required fields are marked *