PHP RFC: Allow NULL
- Version: 1.3
- Voting Start: ?
- Voting End: ?
- RFC Started: 2021-12-23
- RFC Updated: 2022-02-20
- Author: Craig Francis, craig#at#craigfrancis.co.uk
- Status: Draft
- First Published at: https://wiki.php.net/rfc/allow_null
- GitHub Repo: https://github.com/craigfrancis/php-allow-null-rfc
- Implementation: ?
This RFC has been superseded by NULL Coercion Consistency
Introduction
PHP 8.1 introduced “Deprecate passing null to non-nullable arguments of internal functions” (short discussion), which is making it difficult (time consuming) for developers to upgrade.
This has introduced an inconstancy when not using strict_types=1, because most variables types (e.g. integers) can be coerced to the relevant type, but NULL has been deprecated.
In PHP NULL is often used to represent something; e.g. when a GET/POST/COOKIE variable is undefined:
$name = ($_POST['name'] ?? NULL); $name = $request->input('name'); // Laravel $name = $request->get('name'); // Symfony $name = $this->request->getQuery('name'); // CakePHP $name = $request->getGet('name'); // CodeIgniter
And NULL can be returned from functions, e.g.
- array_pop()
- filter_input()
- mysqli_fetch_row()
- error_get_last()
- json_decode()
This makes it common for NULL to be passed to many internal functions, e.g.
trim($name); strtoupper($name); strlen($name); urlencode($name); htmlspecialchars($name); hash('sha256', $name); preg_match('/^[a-z]/', $name); setcookie('name', $name); socket_write($socket, $name); xmlwriter_text($writer, $name);
Developers also use NULL to skip certain parameters, e.g. $additional_headers in mail().
Where NULL has always been coerced into an empty string, the integer 0, the boolean false, etc.
Currently the deprecation notices only affect those using PHP 8.1 with E_DEPRECATED, but it implies everyone will need to modify their code to avoid Fatal Errors in PHP 9.0.
Developers using strict_types=1 may find some value in this, but it's excessive and inconstant for everyone else.
And while individual changes are easy, there are many of them (time consuming), difficult to find, and often pointless, e.g.
- urlencode(strval($name));
- urlencode((string) $name);
- urlencode($name ?? '');
To find these issues, developers need to either - use these deprecation notices, or use very strict Static Analysis (one that can determine when a variable can be NULL; e.g. Psalm at level 3, with no baseline).
It's worth noting that some parameters, like $separator in explode(), already have a “cannot be empty” Fatal Error. So it might be useful to have a separate RFC to update some of these parameters to consistently reject NULL or Empty Strings, e.g. $needle in strpos() and $json in json_decode().
Proposal
There are 3 possible approaches:
- NULL triggers a Fatal Error with strict_types=1, otherwise allow coercion (like how integers can be coerced to a string).
- NULL triggers a Fatal Error for everyone, but update some parameters to explicitly allow NULL (e.g. ?string).
- NULL triggers a Fatal Error for everyone (forget about backwards compatibility).
This needs to be done before the eventual end of the deprecation period, and TypeError exceptions are thrown.
If we choose to “update some parameters to explicitly allow NULL”, there is a draft list of functions, where the bold are being proposed (currently only includes string parameters). Suggestions and Pull Requests welcome.
There is also a Maybe List, where the more questionable arguments end with a “!”. For example, strrpos() accepting an empty string for $needle is wired in itself, and sodium_crypto_box_open() should never receive a blank $ciphertext. And there is an Other List, which can be ignored.
Decision Process
Does NULL for this parameter justify a Fatal Error? e.g.
- preg_match() should deprecate NULL for $pattern (“empty regular expression” warning).
- preg_match() should accept NULL for $subject (e.g. checking user input).
- hash_file() should deprecate NULL for the $filename.
- hash() should accept NULL for $data.
- substr_count() should deprecate NULL for $needle (“$needle cannot be empty” error).
- mb_convert_encoding() should deprecate NULL for $to_encoding (requires a valid encoding).
Backward Incompatible Changes
None
Proposed PHP Version(s)
PHP 8.2
RFC Impact
To SAPIs
None known
To Existing Extensions
None known
To Opcache
None known
Open Issues
Is the list of functions complete?
Future Scope
Some function parameters could be updated to complain when an NULL or Empty String is provided; e.g. $method in method_exists(), or $characters in trim().
Voting
Accept the RFC
TODO
Tests
To get and Test the list of functions, I wrote a script to get_defined_functions(), then used ReflectionFunction() to identify parameters that accepted the 'string' type, and not ->allowsNull(). This resulted in the list of functions to change, where I manually removed the functions that shouldn't be changed, and updated the script to test every argument (to see that it complained with NULL, and the output remained the same) - Source.
Implementation
https://github.com/craigfrancis/php-src/compare/master...allow-null
This patch defines Z_PARAM_STR_ALLOW_NULL.
It works a bit like Z_PARAM_STR_OR_NULL, but it will return an empty string instead of NULL.
It's a fairly easy drop in replacement for Z_PARAM_STR, e.g. htmlspecialchars().
Rejected Features
TODO
Notes
Interesting the example quote from Rasmus is:
PHP is and should remain:
1) a pragmatic web-focused language
2) a loosely typed language
3) a language which caters to the skill-levels and platforms of a wide range of users