For many years I've been missing the undefined value in PHP.
With arrays, there is a disctinciton between null value and missing value (checked with array_key_exists()).
However, if you want to bring the same concept to typed function parameters or class properties, there's nothing
in the native PHP language features to help you.
Normally, you'd use null to represent the absence of a value.
But it's not universally applicable: in some contexts null can be a valid value by itself,
leaving no standard way to represent the absence of a value.
One simple example would be an update request that can set or unset an organization for a user
with an optional "organization" property.
In Typescript it's easy to define it:
interface UserUpdateRequest { name?: string; email?: string; organization?: string | null; }
But how to achieve this in PHP?
Too bad the question marks syntax is already taken for nullable types since PHP 7.1.
Undefined in PHP
Thanks to PHP8 enums and union types, you can have almost full support for undefined values in PHP code by using a simple trick.
All you need is to introduce an enum just for this:
enum Undefined { case VALUE; }
As simple as that!
With this enum you can have a properly typed value object class representing the user update request payload from the above:
final readonly class UserUpdateRequest { public function __construct( public string | Undefined $name = Undefined::VALUE, public string | Undefined $email = Undefined::VALUE, public string | null | Undefined $organization = Undefined::VALUE, ) { // Nothing } }
You can now construct these payload objects providing only the values you have in the request.
The rest will remain Undefined:
$payload = new UserUpdateRequest(name: 'Sherlock Holmes');
Immutable objects
Another nice application of undefined can provide a minimalistic and elegant (yet fully typed) "update" method for changing immutable objects:
final readonly class Address { public function __construct( public string $name, public string $line1, public string $line2 = '', public string $city, public string | null $state = null, public string $zip, public string $country, ) { // Nothing } public function with( string | Undefined $name = Undefined::VALUE, string | Undefined $line1 = Undefined::VALUE, string | Undefined $line2 = Undefined::VALUE, string | Undefined $city = Undefined::VALUE, string | null | Undefined $state = Undefined::VALUE, string | Undefined $zip = Undefined::VALUE, string | Undefined $country = Undefined::VALUE, ) { return new self( name: $name !== Undefined::VALUE ? $name : $this->name, line1: $line1 !== Undefined::VALUE ? $line1 : $this->line1, line2: $line2 !== Undefined::VALUE ? $line2 : $this->line2, city: $city !== Undefined::VALUE ? $city : $this->city, state: $state !== Undefined::VALUE ? $state : $this->state, zip: $zip !== Undefined::VALUE ? $zip : $this->zip, country: $country !== Undefined::VALUE ? $country : $this->country, ); } } $address = new Address( name: 'Sherlock Holmes', line1: '221B Baker Street', city: 'London', zip: 'NW1 6XE', country: 'England', ); $address = $address->with(name: 'Dr. John H. Watson');
Even though the with() method is a little bit verbose, I still consider it minimal:
it provides one single consistent entry point for changing this immutable object,
while keeping all the goodness of fully typed method arguments.
Much better than having separate dedicated with___() methods one for each class property.
Technically Undefined
Today I've published a package (technically/undefined) that provides Undefined enum
to represent undefined values in properties and arguments types, and to use it for default values.
As well as a set of useful utility functions to check for empty and undefined values,
coalesce() a list of values, etc.
The code is available on Github under MIT license:
๐ technically-php/undefined

Check the full documentation in the README.
It's also published on Packagist as a composer package:
๐ฆ technically/undefined.
Install it with Composer:
composer require technically/undefined
Let me know what you think about it.
๐ Cheers!