在 PHP 8 中处理数组

除了性能上的改进,PHP 8 在数组处理方面的两个主要变化涉及负偏移量的处理和大括号 ({}) 的使用。由于这两项变化都可能导致 PHP 8 移植后的应用程序代码中断,因此在这里介绍一下很重要。意识到这里提出的问题,您就有更好的机会在短时间内使损坏的代码恢复正常。

让我们先看看负数组偏移的处理。

处理负偏移量

在 PHP 中为数组赋值时,如果不指定索引,PHP 将自动为您指定一个索引。以这种方式选择的索引是一个整数,它代表的值比当前分配的最高整数键高一。如果尚未分配整数索引键,自动索引分配算法将从零开始。

但在 PHP 7 及以下版本中,这种算法在负整数索引的情况下应用不一致。如果数字数组的索引以负数开始,自动索引就会跳转到零(0),而不管下一个数字通常是多少。而在 PHP 8 中,无论索引是负整数还是正整数,自动索引都会以+1 的值递增。

如果您的代码依赖于自动索引,而任何起始索引都是负数,就可能出现向后兼容的代码中断。由于自动索引是无声发生的,没有任何警告或通知,因此很难检测到这一问题。

下面的代码示例说明了 PHP 7 和 PHP 8 之间的行为差异:

  1. 首先,我们定义一个只以负整数为索引的数组。我们使用 var_dump() 来显示这个数组:

    // /repo/ch06/php8_array_negative_index.php
    $a = [-3 => 'CCC', -2 => 'BBB', -1 => 'AAA'];
    var_dump($a);
    php
  2. 然后,我们定义第二个数组,并将第一个索引初始化为 -3。然后,我们添加额外的数组元素,但不指定索引。这样就会产生自动索引:

    $b[-3] = 'CCC';
    $b[] = 'BBB';
    $b[] = 'AAA';
    var_dump($b);
    php
  3. 如果我们在 PHP 7 中运行该程序,会发现第一个数组的渲染结果是正确的。在 PHP 7 和更早的版本中,只要是直接赋值,数组索引完全可以是负数。下面是输出结果:

    root@php8_tips_php7 [ /repo/ch06 ]#
    php php8_array_negative_index.php
    /repo/ch06/php8_array_negative_index.php:6:
    array(3) {
        [-3] => string(3) "CCC"
        [-2] => string(3) "BBB"
        [-1] => string(3) "AAA"
    }
    /repo/ch06/php8_array_negative_index.php:12:
    array(3) {
        [-3] => string(3) "CCC"
        [0] => string(3) "BBB"
        [1] => string(3) "AAA"
    }
    bash
  4. 但是,从 var_dump() 的第二个输出中可以看到,自动数组索引会跳到零,而不管之前的高值是多少。

  5. 而在 PHP 8 中,输出结果是一致的。下面是 PHP 8 的输出结果:

    root@php8_tips_php8 [ /repo/ch06 ]#
    php php8_array_negative_index.php
    array(3) {
        [-3]=> string(3) "CCC"
        [-2]=> string(3) "BBB"
        [-1]=> string(3) "AAA"
    }
    array(3) {
        [-3]=> string(3) "CCC"
        [-2]=> string(3) "BBB"
        [-1]=> string(3) "AAA"
    }
    bash
  6. 从输出中可以看到,数组索引已自动分配,并以 1 为增量,使两个数组完全相同。

    有关该增强功能的更多信息,请参阅本文: https://wiki.php.net/rfc/negative_array_index

现在,您已经了解了涉及负值的索引自动分配的潜在代码漏洞,让我们把注意力转向另一个值得关注的方面:大括号的使用。

处理花括号使用变化

对于创建 PHP 代码的开发人员来说,大括号 ({}) 是再熟悉不过的了。PHP 语言是用 C 语言编写的,其中大量使用了 C 语言的语法,包括大括号。众所周知,大括号用于在控制结构(例如 if {})、循环(例如 for () {})、函数(例如 function xyz() {})和类中划分代码块。

不过,在本小节中,我们将只讨论与变量相关的大括号用法。PHP 8 中一个潜在的重大变化是使用大括号来标识数组元素。从 PHP 8 开始,使用大括号指定数组偏移量的做法已被弃用。

由于以下原因,旧的用法一直备受争议:

  • 它的使用很容易与双引号字符串内的大括号混淆。

  • 大括号不能用于数组赋值。因此,PHP 核心团队需要使大括号的使用与方括号([ ])…​一致或者干脆取消大括号的使用。最后的决定是取消对数组的大括号支持。

有关此更改背后背景的更多信息,请参阅以下链接: https://wiki.php.net/rfc/deprecate_curly_braces_array_access

下面的代码示例可以说明这一点:

  1. 首先,我们定义了一个回调数组,以说明删除或非法使用大括号的情况:

    // /repo/ch06/php7_curly_brace_usage.php
    $func = [
        1 => function () {
            $a = ['A' => 111, 'B' => 222, 'C' => 333];
            echo 'WORKS: ' . $a{'C'} . "\n";},
        2 => function () {
            eval('$a = {"A","B","C"};');
        },
        3 => function () {
            eval('$a = ["A","B"]; $a{} = "C";');
        }
    ];
    php
  2. 然后,我们使用 try/catch 块对回调进行循环,以捕捉抛出的错误:

    foreach ($func as $example => $callback) {
        try {
            echo "\nTesting Example $example\n";
            $callback();
        } catch (Throwable $t) {
            echo $t->getMessage() . "\n";
        }
    }
    php

如果我们在 PHP 7 中运行该示例,第一个回调可以正常工作。第二个和第三个回调会导致抛出 ParseError

root@php8_tips_php7 [ /repo/ch06 ]#
php php7_curly_brace_usage.php
Testing Example 1
WORKS: 333
Testing Example 2
syntax error, unexpected '{'
Testing Example 3
syntax error, unexpected '}'
bash

然而,当我们在 PHP 8 中运行同一示例时,所有示例都无法运行。下面是 PHP 8 的输出结果:

root@php8_tips_php8 [ /repo/ch06 ]#
php php7_curly_brace_usage.php
PHP Fatal error: Array and string offset access syntax with
curly braces is no longer supported in /repo/ch06/php7_curly_
brace_usage.php on line 8
bash

这种潜在的代码中断很容易检测到。不过,由于您的代码中有很多大括号,您可能需要等待抛出致命错误(Fatal Error)才能捕捉到代码中断。

现在您对 PHP 8 中数组处理的变化有了一定的了解,让我们来看看安全相关函数的变化。