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
608

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...

About15% of developers usestrict_types=1, so you are among the majority who don't.

You canignore 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 firstpart, 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

335parametersaffected by this.

There is also an extra

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

Psalmis 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 secondpart 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 usestrval()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 RFCis 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 (andthe 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 applystrval()to everything, likeprune(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 thenullcase, a slightly cleaner fix is tostrlen($row ?? '')use the "null coalescing operator".

    In most cases the two are probably equivalent, but withstrict_types=1in 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 onisset, not=== null, sois undefinedVariables 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
      About us Disclaimer Sitemap
      php.cn:Public welfare online PHP training,Help PHP learners grow quickly!