rfc:null_coercion_consistency

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
rfc:null_coercion_consistency [2022/05/03 17:40] – Add links to show mt_rand(NULL, NULL) not really happening. craigfrancisrfc:null_coercion_consistency [2022/05/11 10:49] – Add round() example craigfrancis
Line 22: Line 22:
 Roughly **33%** of developers use static analysis (realistically it's less than this, details below). Roughly **33%** of developers use static analysis (realistically it's less than this, details below).
  
-There was a [[https://externals.io/message/112327|short discussion]] about the original RFC, but with the exception of Craig Duncan, there was no consideration for the problems this creates with existing code (or the inconsistency of NULL coercion compared to string/int/float/bool coercion; or other contexts like string concatenation, == comparisons, sprintf, etc).+There was a [[https://externals.io/message/112327|short discussion]] about the original RFC, but with the exception of Craig Duncan, there was no consideration for the problems this creates with existing code (or the inconsistency of NULL coercion compared to string/int/float/bool coercion; or other contexts like string concatenation, == comparisons, arithmetics, sprintf, etc).
  
 The intention is to also keep [[https://github.com/Girgias/unify-typing-modes-rfc|Unify PHP's typing modes]] by George Peter Banyard in mind, with coercions like //substr($string, "offset")// and //htmlspecialchars(array())// as being clearly problematic; whereas the following is common, and has been fine: The intention is to also keep [[https://github.com/Girgias/unify-typing-modes-rfc|Unify PHP's typing modes]] by George Peter Banyard in mind, with coercions like //substr($string, "offset")// and //htmlspecialchars(array())// as being clearly problematic; whereas the following is common, and has been fine:
Line 45: Line 45:
   - [[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>
Line 55: Line 55:
 print(false); print(false);
 print(NULL); // Fine, coerced to empty string. print(NULL); // Fine, coerced to empty string.
 +
 +var_dump(3 + '5' + NULL); // Fine, int(8)
 +var_dump(NULL / 6); // Fine, int(0)
  
 $o = []; $o = [];
Line 107: Line 110:
 String/Int/Float/Bool can be coerced. String/Int/Float/Bool can be coerced.
  
-NULL can usually be coerced (e.g. string concat, == comparison, sprintf, print, array keys), but...+NULL can usually be coerced (e.g. string concatenation, == comparisons, arithmetics, sprintf, print, echo, array keys), but...
  
   - PHP 7.0 introduced the ability for user-defined functions to specify parameter types via the [[https://wiki.php.net/rfc/scalar_type_hints_v5#behaviour_of_weak_type_checks|Scalar Type Declarations RFC]], where the implementation triggered Type Errors for those using //strict_types=1//, and otherwise used coercion for string/int/float/bool, but not NULL.   - PHP 7.0 introduced the ability for user-defined functions to specify parameter types via the [[https://wiki.php.net/rfc/scalar_type_hints_v5#behaviour_of_weak_type_checks|Scalar Type Declarations RFC]], where the implementation triggered Type Errors for those using //strict_types=1//, and otherwise used coercion for string/int/float/bool, but not NULL.
Line 113: Line 116:
  
 ==== 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> 
- 
-Note how line 1 cannot simply be updated to force //$name// to be non-nullable, as it would break the "Add Account" link. But does passing NULL to //htmlspecialchars()// and //trim()// justify a Fatal Type Error? 
- 
-I'd argue this very strict level of type checking 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:
Line 158: Line 131:
 $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>
  
Line 166: Line 139:
  
 <code php> <code php>
 +$rounded_value = round($value);
 +
 $search_trimmed = trim($search); $search_trimmed = trim($search);
  
Line 196: Line 171:
 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]].
Line 338: Line 317:
 ===== 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) =====
Line 391: Line 387:
 ===== 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.
rfc/null_coercion_consistency.txt · Last modified: 2023/10/18 11:57 by craigfrancis