Aktualisieren der vorhandenen Codebasis auf PHP 8.1: Behandeln nicht nullbarer interner Funktionsparameter mit Nullwerten
P粉344355715
P粉344355715 2023-10-31 20:01:48
0
2
665

Ich habe gerade damit begonnen, meinen Code zu aktualisieren, damit er mit PHP 8.1 kompatibel ist. Ich habe viele Codefragmente, in denen ich potenzielle Nullwerte an innere Funktionen übergebe.

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

wobei $row aus einer Quelle (z. B. einer Abfrage) stammt, die möglicherweise Nullwerte hat. Dies kann in diesem Fall zu einer veralteten Warnung führen:

VERALTET: strlen(): Die Übergabe von Null an Parameter #1 vom Typ string ist veraltet ($string)

Ich suche nach der einfachsten und zeiteffizientesten Möglichkeit, die Aktualisierung dieses Codes durchzuführen, z. B. nach einer Korrektur, wo globales Suchen und Ersetzen durchgeführt werden kann. Es scheint die Variable, die ich an die innere Funktion übergebe, zu typisieren, ohne die Funktionalität zu ändern.

error_reporting(E_ALL);
$row = null;

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

Gibt es irgendwelche Probleme mit diesem internen Funktionalitätsansatz, abgesehen von den moralischen Aspekten einer solchen Codierung? Gibt es einen besseren Weg (außer den Code komplett neu zu schreiben und Nullwerte anders zu behandeln)? Ich bevorzuge, dass diese Lösung abwärtskompatibel mit Version 7.4 ist, obwohl ich wahrscheinlich mit Version 8.0 kompatibel sein werde.

Ich weiß, dass es für meine benutzerdefinierte Funktion noch andere Optionen gibt.

P粉344355715
P粉344355715

Antworte allen(2)
P粉436410586

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

简而言之,你不能。


首先,一些背景...

大约15% 的开发者使用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 强制就这样工作了,我假设从一开始,并且也有记录:

  • To String:“null 始终转换为空字符串。”
  • 转为整数:“null 始终转换为零(0)。”
  • 浮动:“对于其他类型的值,转换的方式是先将值转换为 int,然后再转换为 float”
  • 转为布尔值:“当转换为布尔值时,以下值被视为 false [...] 特殊类型 NULL”

无论如何,要修复...第一部分,它会尝试查找您需要更新的代码。

只要可以将 NULL 传递给这些函数参数之一,就会发生这种情况。

至少有 335 受此影响的参数

还有一个额外的104,它们是有点可疑;和 558 其中 NULL 有问题,你应该在哪里修复这些问题,例如define(NULL, '值')

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

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

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

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

我无法使用 PHPStan、Rector、PHP CodeSniffer、PHP CS Fixer 或 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()...但要小心,您的代码可能会通过 ($a === NULL),并且您不想破坏它。

许多开发人员不会意识到他们的某些变量可以包含 NULL - 例如期望

(他们创建的)始终提交所有输入字段;由于网络问题、浏览器扩展、用户在浏览器中编辑 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() 应用于所有事情,例如修剪(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);
P粉087074897

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

在大多数情况下,两者可能是等效的,但在 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

另一方面,请注意 ?? 运算符基于 isset,而不是 === 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'

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

echo strlen($row === null ? '' : $row);
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!