Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision |
rfc:null_coercion_consistency [2022/05/05 11:51] – Add note about html templating engines craigfrancis | rfc:null_coercion_consistency [2022/05/11 10:49] – Add round() example craigfrancis |
---|
- [[https://www.php.net/manual/en/language.types.boolean.php|To Boolean]]: "When converting to bool, the following values are considered false [...] the special type NULL" | - [[https://www.php.net/manual/en/language.types.boolean.php|To Boolean]]: "When converting to bool, the following values are considered false [...] the special type NULL" |
| |
For example: | ==== Current State ==== |
| |
<code php> | <code php> |
| |
==== Examples ==== | ==== Examples ==== |
| |
A simple search page: | |
| |
<code php> | |
$name = $request->get('name'); | |
| |
if (trim($name) === '') { // Contains non-whitespace characters; so not "", " ", NULL, etc | |
$where_sql[] = 'name LIKE ?'; | |
$where_val[] = $name; | |
} | |
| |
echo ' | |
<form action="./" method="get"> | |
<label> | |
Search | |
<input type="search" name="name" value="' . htmlspecialchars($name) . '"> | |
</label> | |
<input type="submit" value="Go"> | |
</form>'; | |
| |
if ($name !== NULL) { | |
$register_url = '/admin/accounts/add/?name=' . urlencode($name); | |
echo ' | |
<p><a href="' . htmlspecialchars($register_url) . '">Add Account</a></p>'; | |
} | |
</code> | |
| |
Regarding the source of //$name// (line 1); while frameworks could change their default to an Empty String, or an automated tool could cast the variable to a string, doing so would break the "Add Account" link. | |
| |
HTML Templating engines like [[https://github.com/laravel/framework/blob/ab1506091b9f166b312b3990d07b2e21d971f2e6/src/Illuminate/Support/helpers.php#L119|Laravel Blade]] will now suppress this deprecation via null-coalescing ([[https://github.com/laravel/framework/pull/36262/files#diff-15b0a3e2eb2d683222d19dfacc04c616a3db4e3d3b3517e96e196ccbf838f59eR118|patch]]); or [[https://github.com/twigphp/Twig/blob/b4d6723715da57667cca851051eba3786714290d/src/Extension/EscaperExtension.php#L195|Symphony Twig]] will preserve NULL, where it's usually passed to //echo// (despite the [[https://www.php.net/echo|echo documentation]] saying it only accepts non-nullable strings). | |
| |
I'd argue a very strict level of type checking (that prevents all forms of coercion) is best done by Static Analysis, which can check if a variable can be nullable, and it can decide if this is a problem, in the same way that a string (e.g. '15') being provided to integer parameter could be seen as a problem. | |
| |
Common sources of NULL: | Common sources of NULL: |
$search = $request->getGet('q'); // CodeIgniter | $search = $request->getGet('q'); // CodeIgniter |
| |
$value = array_pop($empty_array); | |
$value = mysqli_fetch_row($result); | $value = mysqli_fetch_row($result); |
$value = json_decode($json); // Invalid JSON, or nesting limit. | $value = json_decode($json); // Invalid JSON, or nesting limit. |
| $value = array_pop($empty_array); |
</code> | </code> |
| |
| |
<code php> | <code php> |
| $rounded_value = round($value); |
| |
$search_trimmed = trim($search); | $search_trimmed = trim($search); |
| |
mail('nobody@example.com', 'subject', 'message', NULL, '-fwebmaster@example.com'); | mail('nobody@example.com', 'subject', 'message', NULL, '-fwebmaster@example.com'); |
</code> | </code> |
| |
| HTML Templating engines like [[https://github.com/laravel/framework/blob/ab1506091b9f166b312b3990d07b2e21d971f2e6/src/Illuminate/Support/helpers.php#L119|Laravel Blade]] suppress this deprecation with null-coalescing ([[https://github.com/laravel/framework/pull/36262/files#diff-15b0a3e2eb2d683222d19dfacc04c616a3db4e3d3b3517e96e196ccbf838f59eR118|patch]]); or [[https://github.com/twigphp/Twig/blob/b4d6723715da57667cca851051eba3786714290d/src/Extension/EscaperExtension.php#L195|Symphony Twig]] which preserves NULL, but it's often passed to //echo// (which accepts it, despite the [[https://www.php.net/echo|echo documentation]] saying it accepts non-nullable strings). |
| |
| I'd argue a very strict level of type checking (that prevents all forms of coercion) is best done by Static Analysis, which can check if a variable can be nullable, and it can decide if this is a problem, in the same way that a string (e.g. '15') being provided to integer parameter could be seen as a problem. |
| |
There are approximately [[https://github.com/craigfrancis/php-allow-null-rfc/blob/main/functions-change.md|335 parameters affected by this deprecation]]. | There are approximately [[https://github.com/craigfrancis/php-allow-null-rfc/blob/main/functions-change.md|335 parameters affected by this deprecation]]. |
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== |
| |
None | While the intention of this RFC is to avoid a BC break; for user defined functions to be updated to also coerce NULL (instead of throwing a Type Error), it's possible some code may rely on that behaviour, for example: |
| |
| <code php> |
| function my_function(string $my_string) { |
| var_dump($my_string); |
| } |
| |
| try { |
| my_function('A'); // string(1) "A" |
| my_function(1); // string(1) "1" |
| my_function(1.2); // string(3) "1.2" |
| my_function(true); // string(1) "1" |
| my_function(false); // string(0) "" |
| my_function(NULL); // Throw Type Error |
| } catch (TypeError $e) { |
| // Do something important? |
| } |
| </code> |
| |
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== |
===== Notes ===== | ===== Notes ===== |
| |
The **15%** of scripts that do not use //strict_types=1// was calculated using [[https://grep.app/|grep.app]], to "search across a half million git repos", were each result is a script (not a count of matches, [[https://grep.app/search?q=defuse/php-encryption&filter[lang][0]=PHP|example]]). We can see [[https://grep.app/search?q=strict_types&filter[lang][0]=PHP|272,871]] scripts using //strict_types=1//, out of [[https://grep.app/search?q=php&filter[lang][0]=PHP|1,842,666]]. And keep in mind that [[https://grep.app/search?q=class%20wpdb%20%7B&filter[lang][0]=PHP|WordPress only really appears once]], it is [[https://make.wordpress.org/core/2022/01/10/wordpress-5-9-and-php-8-0-8-1/#php-8-1-deprecation-passing-null-to-non-nullable-php-native-functions-parameters|affected by this deprecation]], and is installed/used by many. | The **15%** of scripts that use //strict_types=1// was calculated using [[https://grep.app/|grep.app]], to "search across a half million git repos", were each result is a script (not a count of matches, [[https://grep.app/search?q=defuse/php-encryption&filter[lang][0]=PHP|example]]). We can see [[https://grep.app/search?q=strict_types&filter[lang][0]=PHP|272,871]] scripts using //strict_types=1//, out of [[https://grep.app/search?q=php&filter[lang][0]=PHP|1,842,666]]. And keep in mind that [[https://grep.app/search?q=class%20wpdb%20%7B&filter[lang][0]=PHP|WordPress only really appears once]], it is [[https://make.wordpress.org/core/2022/01/10/wordpress-5-9-and-php-8-0-8-1/#php-8-1-deprecation-passing-null-to-non-nullable-php-native-functions-parameters|affected by this deprecation]], and is installed/used by many. |
| |
In the [[https://wiki.php.net/rfc/scalar_type_hints_v5#behaviour_of_weak_type_checks|Scalar Type Declarations]] RFC for PHP 7.0, scalar types were defined as "int, float, string and bool" - but, despite NULL also being a simple value (i.e. not an array/object/resource), it was not included in this definition. For backwards compatibility reasons this definition is unlikely to change. | In the [[https://wiki.php.net/rfc/scalar_type_hints_v5#behaviour_of_weak_type_checks|Scalar Type Declarations]] RFC for PHP 7.0, scalar types were defined as "int, float, string and bool" - but, despite NULL also being a simple value (i.e. not an array/object/resource), it was not included in this definition. For backwards compatibility reasons this definition is unlikely to change. |