将 null 传递给不可为 null 的内部函数参数 - 将现有代码库更新到 php 8.1

mse*_*ert 7 php null php-8.1

我刚刚开始升级我的代码以兼容 php 8.1。我有很多代码片段,其中我将潜在的空值传递给内部函数。

if (strlen($row) > 0) {
   ...
} 
Run Code Online (Sandbox Code Playgroud)

其中 $row 来自可能具有空值的源(例如查询)。这可能会生成弃用警告;在这种情况下:

已弃用:strlen():已弃用将 null 传递给字符串类型的参数 #1 ($string)

我正在寻找最简单、最省时的方法来处理升级此代码,例如修复可以进行全局搜索和替换的位置。似乎对我传递给内部函数的变量进行类型转换可以在不改变功能的情况下工作。

error_reporting(E_ALL);
$row = null;

if (strlen((string) $row) > 0) {
   ...
}
Run Code Online (Sandbox Code Playgroud)

除了以这种方式编码的道德方面之外,这种内部功能方法是否存在问题?有没有更好的方法(除了完全重写代码并以不同的方式处理空值之外)?我更喜欢这个向后兼容 v7.4 的解决方案,尽管我可能会兼容 8.0。

我知道我的用户定义函数还有其他选择。

Cra*_*cis 9

回答有关“处理升级此代码的最简单、最省时的方法”的问题。

\n

简而言之,你不能。

\n
\n

首先,一些背景...

\n

大约15% 的开发人员使用strict_types=1,因此您属于大多数不使用 的开发人员。

\n

您现在可以忽略这个问题(弃用),但是 PHP 9.0 会将其设为致命类型错误,从而导致很多问题。

\n

也就是说,您仍然可以将字符串与 NULL 连接:

\n
$name = NULL;\n$a = \'Hi \' . $name;\n
Run Code Online (Sandbox Code Playgroud)\n

您仍然可以将 NULL 与空字符串进行比较:

\n
if (\'\' == NULL) {\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您仍然可以使用 NULL 进行计算(它仍然被视为 0):

\n
var_dump(3 + \'5\' + NULL); // Fine, int(8)\nvar_dump(NULL / 6); // Fine, int(0)\n
Run Code Online (Sandbox Code Playgroud)\n

你仍然可以打印/回显 NULL:

\n
print(NULL);\necho NULL;\n
Run Code Online (Sandbox Code Playgroud)\n

您仍然可以将 NULL 传递给sprintf()并将其强制为空字符串%s,例如

\n
sprintf(\'%s\', NULL);\n
Run Code Online (Sandbox Code Playgroud)\n

你仍然可以强制其他值(遵循规则),例如

\n
strlen(15);\nhtmlspecialchars(1.2);\nsetcookie(\'c\', false);\n
Run Code Online (Sandbox Code Playgroud)\n

我假设从一开始,NULL 强制就这样工作,并且也有记录:

\n
    \n
  • To String : \xe2\x80\x9cnull 始终转换为空字符串。\xe2\x80\x9d
  • \n
  • 转为整数:\xe2\x80\x9cnull 始终转换为零 (0)。\xe2\x80\x9d
  • \n
  • To Float : \xe2\x80\x9c 对于其他类型的值,先将值转换为 int,然后再转换为 float\xe2\x80\x9d
  • \n
  • To Boolean : \xe2\x80\x9c 当转换为 bool 时,以下值被视为 false [...] 特殊类型 NULL\xe2\x80\x9d
  • \n
\n
\n

不管怎样,要修复...第一部分它试图找到您需要更新的代码。

\n

每当 NULL可以传递给这些函数参数之一时,就会发生这种情况。

\n

至少有335 个参数受此影响

\n

还有一个额外的104 有点值得怀疑;和558,其中 NULL 是有问题的,您应该修复这些问题,例如define(NULL, \'value\')

\n

诗篇是我能找到的唯一能够对此提供帮助的工具。

\n

诗篇需要处于非常高的检查级别(1、2 或 3)。

\n

而且您不能使用基线来忽略问题(开发人员在现有项目中引入静态分析的技术,因此它只检查新的/编辑的代码)。

\n

如果您以前没有使用过静态分析工具(不用担心,建议只有33% 的开发人员使用过);然后预计会花费大量时间修改代码(从第 8 级开始,最宽松,然后慢慢提高)。

\n

我无法让 PHPStan、Rector、PHP CodeSniffer、PHP CS Fixer 或 PHPCompatibility 来查找这些问题(结果);Juliette 已经证实,通过 PHPCompatibility 来解决这个问题将“非常困难”,因为它“无法可靠地嗅探”(来源)。

\n
\n

一旦找到每个问题,第二部分就是编辑。

\n

不可能引起问题的地方是更换水槽,例如

\n
example_function(strval($name));\nexample_function((string) $name);\nexample_function($name ?? \'\');\n
Run Code Online (Sandbox Code Playgroud)\n

或者,您可以尝试追溯到变量的源,并尝试首先阻止将其设置为 NULL。

\n

以下是一些非常常见的 NULL 来源:

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

其中一些函数需要第二个参数来指定默认值,或者您可以strval()更早地使用...但要小心,您的代码可能会专门检查 NULL via ($a === NULL),并且您不想破坏它。

\n

许多开发人员不会意识到他们的某些变量可以包含 NULL - 例如期望<form>(他们创建的)始终提交所有输入字段;由于网络问题、浏览器扩展、用户在浏览器中编辑 DOM/URL 等,这种情况可能不会发生。

\n
\n

我花了一年的大部分时间来研究这个问题。

\n

我开始编写两个 RFC 来尝试解决这个问题。第一个是更新一些函数以接受 NULL(这并不理想,因为它让使用 strict_types 的开发人员感到不安);第二个 RFC是允许 NULL 在这种情况下继续被强制...但我没有将其付诸表决,因为我刚刚收到了大量负面反馈,而且我不希望这种拒绝将来会被引用来说明为什么这个问题无法解决(虽然最初的更改几乎没有被讨论,但这个会是)。

\n

似乎 NULL 受到了不同的对待,因为它从未被视为“标量值”——我认为很多开发人员并不关心这种区别,但它时不时会出现。

\n

对于与我合作过的开发人员来说,大多数人都忽略了这个问题(希望稍后能解决它,这可能不是最好的主意);例如

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

一个团队正在努力坚持strval()一切,例如trim(strval($search))。但一年多后他们仍然发现问题(他们表示使用 8.1 alpha 1 进行测试)。

\n

我正在考虑的另一个选择是创建一个库,在命名空间下将所有这些 ~335 个函数重新定义为可空;例如

\n
namespace allow_null_coercion;\n\nfunction strlen(?string $string): int {\n    return \\strlen(\\strval($string));\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后开发人员将包含该库,并自己使用命名空间:

\n
namespace allow_null_coercion;\n\n$search = $request->input(\'q\'); // Could return NULL\n\n// ...\n\necho strlen($search);\n
Run Code Online (Sandbox Code Playgroud)\n


IMS*_*SoP 6

如果您明确尝试处理 的情况,那么使用“空合并运算符”null会是一个稍微干净的修复方法。strlen($row ?? '')

在大多数情况下,两者可能是等效的,但strict_types=1实际上,如果值是可以转换为字符串的其他类型,则它们的行为会有所不同:

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
Run Code Online (Sandbox Code Playgroud)

另一方面,请注意该??运算符基于isset、 not === null,因此未定义的变量的行为会有所不同:

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'
Run Code Online (Sandbox Code Playgroud)

如果您关心这种情况,与旧行为最直接等效的代码会更加冗长:

echo strlen($row === null ? '' : $row);
Run Code Online (Sandbox Code Playgroud)