搜索

首页  >  问答  >  正文

将现有代码库更新到 php 8.1:处理具有 Null 值的不可空内部函数参数

我刚刚开始升级我的代码以兼容 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。

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

P粉344355715P粉344355715388 天前780

全部回复(2)我来回复

  • P粉436410586

    P粉4364105862023-11-01 11:49:53

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

    简而言之,你不能。


    首先,一些背景...

    大约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()...但要小心,您的代码可能会通过 < code>($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);
    

    回复
    0
  • P粉087074897

    P粉0870748972023-11-01 09:34:02

    如果您明确尝试处理 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);

    回复
    0
  • 取消回复