[PHP-DEV] Proposal discussion: distinguishing "never assigned" vs "assigned null"

Hello Internals of PHP,

I’d like to get feedback on a potential language feature before considering whether it deserves an RFC.

Currently, PHP does not provide a general way to distinguish between:

  • a variable or property that has never been assigned, and
  • one that was explicitly assigned the value null.

isset() treats both cases identically, property_exists() only reflects declarations, and ReflectionProperty::isInitialized() works only for typed properties and does not help differentiate explicit null from absence in the broader sense.

There are workarounds involving reflection and string-based variable/property names, but these approaches rely on dynamic access, which is not ideal for developer experience, particularly regarding static analysis, IDE autocompletion, and refactoring safety.

I’m interested in hearing whether there is openness to exploring a built-in mechanism that can reliably distinguish these states at the language level.

At this point I’m only seeking initial feedback to understand whether this aligns with PHP’s direction, and whether it would be worth developing into a formal RFC.

related issue: https://github.com/php/php-src/issues/20663

Thank you for your time.

– Hichem Taboukouyout.

──────────────────────────
Hichem Taboukouyout
Full-stack Developer & Engineer in Industrial and Tertiary Systems (GSIT)

:e_mail: hichem.taboukouyout@hichemtab-tech.me
:globe_with_meridians: hichemtab-tech.me
LinkedIn: https://dz.linkedin.com/in/hichem-taboukouyout
GitHub: github.com/HichemTab-tech

···

Hichem Taboukouyout

Full-stack Developer & Engineer in Industrial and Tertiary Systems (GSIT)

:globe_with_meridians: hichemtab-tech.me

:laptop: github.com/HichemTab-tech

:envelope: hichem.taboukouyout@hichemtab-tech.me

On 09/12/2025 13:59, Hichem Taboukouyout wrote:

Hello Internals of PHP,

I’d like to get feedback on a potential language feature before considering whether it deserves an RFC.

Currently, PHP does not provide a general way to distinguish between:
- a variable or property that has never been assigned, and
- one that was explicitly assigned the value null.

Hi Hichem,

This is something which has been discussed several times over the years, and consensus has generally been against.

My own view is largely unchanged since I wrote these two Stack Overflow answers a decade ago: https://stackoverflow.com/a/18646568/157957 and https://stackoverflow.com/a/18601583/157957

In short: any algorithm which needs to distinguish between "never assigned" and "currently contains null" can and should be rewritten to instead use some other distinction - a separate boolean tracking the state, or additional values which can be distinguished directly.

Key-value maps built with associative arrays or dynamic objects are an exception to this rule, but we already have array_key_exists and property_exists for those use cases.

One thing that has changed is that we now have native, opaque, enums; so many cases assigning specific meaning to null can be rewritten to be more expressive.

For example, instead of this:

/** @var ?string $inputFile */

if ( $inputFile === null ) {
     // do something with standard input
}
else {
     // verify and use the given input file in some way
}

You can write this:

enum InputFileOption { case STDIN; }

/** @var string|InputFileOption $inputFile */

if ( $inputFile === InputFileOption::STDIN ) {
     // do something with standard input
}
else {
     // verify and use the given input file in some way
}

Then instead of wanting a new "very null" check:

/** @var ??string $inputFile */

if ( ! is_assigned($inputFile) ) {
     // complain that the user didn't provide an answer
}
else if ( $inputFile === null ) {
     // do something with standard input
}
else {
     // verify and use the given input file in some way
}

You can just add a case to the enum:

enum InputFileOption { case STDIN; case NOT_CONFIGURED; }

/** @var string|InputFileOption $inputFile */

if ( $inputFile === InputFileOption::NOT_CONFIGURED ) {
     // complain that the user didn't provide an answer
}
else if ( $inputFile === InputFileOption::STDIN ) {
     // do something with standard input
}
else {
     // verify and use the given input file in some way
}

And unlike using "null" and "unassigned" for your special states, you can carry on adding more cases whenever you want.

Regards,

--
Rowan Tommins
[IMSoP]