Updating existing codebase to PHP 8.1: Handling non-nullable internal function parameters with Null values
P粉344355715
P粉344355715 2023-10-31 20:01:48
0
2
785

I just started upgrading my code to be compatible with php 8.1. I have a lot of code snippets where I'm passing potential null values ​​to inner functions.

if (strlen($row) > 0) {
   ...
}

where $row comes from a source (such as a query) that may have null values. This may generate a deprecation warning; in this case:

DEPRECATED: strlen(): Passing null to a parameter of type string is deprecated #1 ($string)

I'm looking for the easiest, most time-efficient way to handle upgrading this code, such as fixing where global search and replace can be done. It seems to typecast the variable I pass to the inner function without changing the functionality.

error_reporting(E_ALL);
$row = null;

if (strlen((string) $row) > 0) {
   ...
}

Are there any issues with this internal functionality approach, aside from the moral aspects of encoding it this way? Is there a better way (other than completely rewriting the code and handling null values ​​differently)? I prefer this solution to be backwards compatible with v7.4, although I'd probably be 8.0 compatible.

I know there are other options for my user defined function.

P粉344355715
P粉344355715

reply all(2)
P粉436410586

Answer to your question about "The easiest, most time-efficient way to handle upgrading this code."

In short, you can't.


First, some background...

About 15% of developers use strict_types=1, so you are among the majority who don't.

You can ignore this issue now (deprecated), but PHP 9.0 will cause a lot of problems by making it a fatal type error.

That said, you can still use NULL connection strings:

$name = NULL;
$a = 'Hi ' . $name;
You can still compare NULL to the empty string:

if ('' == NULL) {
}
And you can still use NULL for calculations (it's still treated as 0):

var_dump(3 + '5' + NULL); // Fine, int(8)
var_dump(NULL / 6); // Fine, int(0)
You can still print/echo NULL:

print(NULL);
echo NULL;
You can still pass NULL into

sprintf() and force it to an empty string using %s, e.g.

sprintf('%s', NULL);
You can still force other values ​​(follow rules) like

strlen(15);
htmlspecialchars(1.2);
setcookie('c', false);
NULL coercion has worked this way since then, I assume from the beginning, and that's documented too:


Anyway, to fix...

The first part, it will try to find the code you need to update.

This occurs whenever NULL

can be passed to one of these function arguments.

There are at least

335 parameters affected by this.

There is also an extra

104, they are a bit fishy ; and 558 where NULL has problems , where should you fix these, e.g. define(NULL, 'value').

Psalm is the only tool I can find that helps with this.

Psalms need to be at a very high inspection level (1, 2 or 3).

And you can't use baselines to ignore problems (techniques where developers introduce static analysis in existing projects, so it only checks new/edited code).

If you haven't used a static analysis tool before (don't worry, it's only recommended for

33% of developers ); then expect to spend a lot of time modifying your code (starting at level 8, max. loose, then slowly increase).

I cannot use PHPStan, Rector, PHP CodeSniffer, PHP CS Fixer, or PHPCompatibility to find these issues (source).


After you find each question, The second part is editing.

The least likely place to cause a problem is to replace the sink, for example

example_function(strval($name));
example_function((string) $name);
example_function($name ?? '');

Alternatively, you could try tracing back to the source of the variable and try preventing it from being set to NULL in the first place.

The following are some very common sources of NULL:

$search = (isset($_GET['q']) ? $_GET['q'] : NULL);
 
$search = ($_GET['q'] ?? NULL); // Fairly common (since PHP 7)
 
$search = filter_input(INPUT_GET, 'q');
 
$search = $request->input('q'); // Laravel
$search = $request->get('q'); // Symfony
$search = $this->request->getQuery('q'); // CakePHP
$search = $request->getGet('q'); // CodeIgniter
 
$value = mysqli_fetch_row($result);
$value = json_decode($json); // Invalid JSON, or nesting limit.
$value = array_pop($empty_array);

Some of these functions require a second parameter to specify a default value, or you can use strval() ahead of time...but be careful, your code may pass ($a = == NULL), and you don't want to break it.

Many developers don't realize that some of their variables can contain NULL - such as expecting

(that they create) to always submit all input fields; due to network issues, browser extensions, users Editing the DOM/URL etc. in the browser, this may not happen.


I have been working on this problem for most of the year.

I started writing two RFCs to try to solve this problem. The first is to update some functions to accept NULL (which is not ideal, as it upsets developers who use strict_types); The second RFC is to allow NULL to continue to be enforced in this case. .....but I didn't put it up for a vote because I just received a ton of negative feedback and I didn't want that rejection referenced in the future to explain why this issue couldn't be fixed (and the initial changes were barely Discussion , this one).

It seems that NULL is handled differently because it is never treated as a "scalar value" - I don't think many developers care about this distinction, but it comes up from time to time.

Most of the developers I've worked with ignore this issue (hoping to fix it later, which is probably not the best idea); e.g.

function ignore_null_coercion($errno, $errstr) {
  // https://github.com/php/php-src/blob/012ef7912a8a0bb7d11b2dc8d108cc859c51e8d7/Zend/zend_API.c#L458
  if ($errno === E_DEPRECATED && preg_match('/Passing null to parameter #.* of type .* is deprecated/', $errstr)) {
    return true;
  }
  return false;
}
set_error_handler('ignore_null_coercion', E_DEPRECATED);

There is a team trying to apply strval() to everything, like prune(strval($search)). But more than a year later they're still seeing issues (they said they were testing with 8.1 alpha 1).

Another option I'm considering is to create a library that redefines all these ~335 functions as nullable under a namespace; e.g.

namespace allow_null_coercion;

function strlen(?string $string): int {
    return \strlen(\strval($string));
}

The developer will then include the library and use the namespace themselves:

namespace allow_null_coercion;

$search = $request->input('q'); // Could return NULL

// ...

echo strlen($search);
P粉087074897

If you are explicitly trying to handle the null case, a slightly cleaner fix is ​​to strlen($row ?? '') use the "null coalescing operator".

In most cases the two are probably equivalent, but with strict_types=1 in effect they will behave differently if the value is of another type that can be converted to a string Difference:

declare(strict_types=1);
$row = 42;
echo strlen($row); // TypeError: must be of type string, int given
echo strlen((string) $row); // Succeeds, outputting '2'
echo strlen($row ?? ''); // TypeError: must be of type string, int given

On the other hand, note that the ?? operator is based on isset, not === null, so is undefined Variables behave differently:

declare(strict_types=1);
$row = [];
echo strlen($row['no_such_key']); // Warning: Undefined array key; TypeError: must be of type string, null given
echo strlen((string) $row['no_such_key']); // Warning: Undefined array key; outputs '0'
echo strlen($row['no_such_key'] ?? ''); // No warning, just outputs '0'

If you care about this case, the most direct equivalent to the old behavior is more verbose:

echo strlen($row === null ? '' : $row);
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template