我刚刚开始将代码升级为兼容 php 8.1。我有许多代码片段,其中我将潜在的空值传递给内部函数。if (strlen($row) > 0) { ....
我刚刚开始将代码升级为兼容 php 8.1。我有许多代码片段,其中我向内部函数传递了可能为空的值。
if (strlen($row) > 0) {
...
}
其中 $row 来自可能具有空值的源(例如查询)。这可能会生成弃用警告;在这种情况下:
已弃用:strlen():已弃用将 null 传递给字符串类型的参数 #1 ($string)
我正在寻找最简单、最省时的方式来处理升级此代码,例如修复可以进行全局搜索和替换的地方。似乎对我传递给内部函数的变量进行类型转换可以在不改变功能的情况下工作。
error_reporting(E_ALL);
$row = null;
if (strlen((string) $row) > 0) {
...
}
除了以这种方式编码的道德方面之外,这种方法对于内部函数是否存在问题?有没有更好的方法(除了完全重写代码并以不同的方式处理空值)?我更喜欢这种向后兼容 v7.4 的解决方案,尽管我可能可以接受 8.0 兼容性。
我知道我的用户定义函数还有其他选择。
回答有关“处理升级此代码的最简单、最省时的方法”的问题。
简而言之,你不能。
首先,介绍一些背景知识...
大约 15% of developers use strict_types=1
,因此您属于不使用此功能的开发人员中的大多数。
您 可以 忽略此问题(弃用),但 PHP 9.0 会使其成为致命类型错误,从而导致很多问题。
也就是说,您仍然可以将字符串与 NULL 连接起来:
$name = NULL;
$a = 'Hi ' . $name;
您仍然可以将 NULL 与空字符串进行比较:
if ('' == NULL) {
}
并且您仍然可以使用 NULL 进行计算(它仍然被视为 0):
var_dump(3 + '5' + NULL); // Fine, int(8)
var_dump(NULL / 6); // Fine, int(0)
你仍然可以打印/回显 NULL:
print(NULL);
echo NULL;
你仍然可以传递 NULL sprintf()
并将其强制转换为空字符串 %s
,例如
sprintf('%s', NULL);
你仍然可以强制其他值(遵循规则),例如
strlen(15);
htmlspecialchars(1.2);
setcookie('c', false);
我认为从一开始 NULL 强制就是这样工作的,并且也有记录:
无论如何,要修复... 第一 部分是尝试找到您需要更新的代码。
只要 将 传递给这些函数参数之一,就会发生这种情况。
至少有 335 parameters affected by this .
另外还有 104 个有点可疑 ;而 558 个 NULL 有问题 ,你应该修复它们,例如 define(NULL, 'value')
.
诗篇 是我能找到的唯一能够帮助解决这个问题的工具。
而且诗篇需要达到非常高的检查水平(1、2 或 3)。
并且您不能使用基线来忽略问题(将静态分析引入现有项目的开发人员使用的一种技术,因此它只检查新的/编辑的代码)。
如果您以前没有使用过静态分析工具(不用担心,据估计只有 33% 的开发人员使用过 );那么您将需要花费大量时间修改代码(从最宽松的第 8 级开始,然后慢慢提高)。
我无法让 PHPStan、Rector、PHP CodeSniffer、PHP CS Fixer 或 PHPCompatibility 发现这些问题 ( 结果 );Juliette 已确认使用 PHPCompatibility 来解决这个问题将是“相当困难的”,因为它“无法可靠地嗅探” ( 来源 )。
一旦找到了每一个问题, 第二 部分就是编辑。
最 不可能 引起问题的地方是更换水槽,例如
example_function(strval($name));
example_function((string) $name);
example_function($name ?? '');
或者,您可以尝试追溯到变量的来源,并尝试首先阻止将其设置为 NULL。
以下是一些非常常见的 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);
其中一些函数采用第二个参数来指定默认值,或者您可以 strval()
更早使用...但要小心,您的代码可能会专门通过检查 NULL ($a === NULL)
,而您不想破坏这一点。
许多开发人员没有意识到他们的某些变量可能包含 NULL - 例如,期望 <form>
(他们创建的)始终提交所有输入字段;由于网络问题、浏览器扩展、用户在浏览器中编辑 DOM/URL 等原因,这可能不会发生。
我已经研究这个问题近一年了。
我开始编写两个 RFC 来尝试解决这个问题。第一个是更新一些函数以接受 NULL(这并不理想,因为它会让使用 strict_types 的开发人员感到不安);第二个 RFC 是允许在此上下文中继续强制使用 NULL...但我没有将其付诸表决,因为我刚刚收到大量负面反馈,并且我不希望将来引用这种拒绝来解释为什么无法修复此问题(虽然最初 的更改几乎没有被讨论 ,但这个会讨论)。
似乎 NULL 被区别对待,因为它从来没有被认为是一个“标量值”——我不认为很多开发人员关心这种区别,但它时不时地会出现。
与我合作过的开发人员中,大多数人都忽略了这个问题(希望它稍后能得到解决,但这可能不是最好的主意);例如
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);
而一个团队则试图坚持 strval()
一切,例如 trim(strval($search))
。但一年多后他们仍然发现问题(他们表示使用 8.1 alpha 1 进行测试)。
我正在考虑的另一个选择是创建一个库,将所有这些~335 个函数重新定义为可空,在命名空间下;例如
namespace allow_null_coercion;
function strlen(?string $string): int {
return \strlen(\strval($string));
}
然后开发人员将包含该库,并自己使用命名空间:
namespace allow_null_coercion;
$search = $request->input('q'); // Could return NULL
// ...
echo strlen($search);