探索新的数据类型

任何入门级 PHP 开发人员都要学习的一件事就是 PHP 有哪些可用的 数据类型 以及如何使用它们。基本数据类型包括 int(整数)、floatbool(布尔)和 string。复杂数据类型包括数组和对象。此外,还有 NULLresource 等其他数据类型。本节将讨论 PHP 8 中引入的一些新数据类型,包括联合类型和混合类型。

切勿混淆 数据类型数据格式,这一点极为重要。本节将介绍数据类型。另一方面,数据格式是一种表示数据的方式,用作传输或存储的一部分。数据格式的例子包括 XML、JavaScript Object Notation (JSON) 和 YAML Ain’t Markup Language (YAML)。

联合类型

与 int 或 string 等其他数据类型不同,需要注意的是没有明确称为 union 的数据类型。相反,当您看到 union 类型的引用时,其含义是 PHP 8 引入了一种新语法,允许您指定类型的联合,而不是只指定一种。现在让我们来看看 union 类型的通用语法。

联合类型语法

联合类型的通用语法如下:

function ( type|type|type $var) {}

您可以提供任何现有的数据类型(例如 float 或 string)来代替 type。不过,有一些限制在大多数情况下是完全合理的。本表总结了比较重要的限制:

Table 1. Table 1.3 – Disallowed union types
Disallowed Not Allowed Example

联合类型中不能包含 void

void|int

混合型不能包含在联合类型中(详情见下文)

mixed|int

Nullable 类型不能包含在联合类型中

string|?array

联合类型中不能同时包含 TRUE 和 FALSE。

TRUE|FALSE

不允许冗余类型

int|float|int|array|iterable|Traversable

从这个例外列表中可以看出,定义联合类型主要是一个常识问题。

最佳实践:在使用联合类型时,如果不执行严格的类型检查,类型强制(PHP 为满足函数的要求而在内部转换数据类型的过程)可能会造成问题。因此,最佳做法是在使用联合类型的文件顶部添加如下内容:

declare(strict_types=1);

联合类型例子

让我们以本章中使用的 SingleChar 类为例进行简单说明。其中一个方法是 colorAlloc()。该方法利用 imagecolorallocate() 函数从图像中分配颜色。它的参数是代表红、绿、蓝的整数值。

为了便于论证,我们假设第一个参数实际上是一个数组,代表三个值—​红、绿、蓝各一个。在这种情况下,第一个值的参数类型不能是 int,否则,如果提供的是一个数组,在开启严格类型检查时就会出错。

在早期的 PHP 版本中,唯一的解决办法就是取消对第一个参数的类型检查,并在相关的 DocBlock 中说明可以接受多种类型。下面是该方法在 PHP 7 中的表现:

/**
 * Allocates a color resource
 *
 * @param array|int $r
 * @param int $g
 * @param int $b]
 * @return int $color
 */
public function colorAlloc($r, $g = 0, $b = 0) {
    if (is_array($r)) {
        [$r, $g, $b] = $r;
    }
    return \imagecolorallocate($this->image, $r, $g, $b);
}

关于第一个参数 $r 的数据类型的唯一提示是 @param array|int $r DocBlock 注释,以及没有与该参数相关的数据类型提示。在 PHP 8 中,利用联合类型的优势,请注意这里的区别:

#[description("Allocates a color resource")]
#[param("int|array r")]
#[int("g")]
#[int("b")]
#[returns("int")]
public function colorAlloc(
    int|array $r, int $g = 0, int $b = 0) {
    if (is_array($r)) {
        [$r, $g, $b] = $r;
    }
    return \imagecolorallocate($this->image, $r, $g, $b);
}

在上例中,除了第一个参数可以接受 arrayint 类型的属性外,在方法签名本身中,int|array 联合类型也清楚地表明了这一选择。

混合型

mixed 是 PHP 8 中引入的另一种新类型。与 union 类型不同,mixed 是一种实际的数据类型,代表类型的最终结合。它用来表示接受任何和所有的数据类型。从某种意义上说,PHP 已经有了这种功能:只需省略数据类型,它就是一个隐含的 mixed 类型!

您会在 PHP 文档中看到对混合类型的引用。PHP 8 将混合类型作为一种实际的数据类型,从而使这种表示形式正规化。

为什么使用混合类型?

稍等片刻—​此时你可能会想:为什么还要使用混合类型呢?为了让你放心,这是一个很好的问题,使用这种类型并没有令人信服的理由。

不过,通过在函数或方法签名中使用混合类型,你可以清楚地表明使用该参数的意图。如果您只是将数据类型留空,其他开发人员在使用或审查您的代码时可能会认为您忘记添加该类型。最起码,他们会不清楚未键入参数的性质。

混合类型对继承的影响

由于 mixed 类型代表了 widening 的终极范例,因此当一个类从另一个类扩展时,可以用它来拓宽数据类型的定义。下面是一个使用混合类型的示例,说明了这一原理:

  1. 首先,我们使用限制性更强的 object 数据类型定义父类,如下所示:

    // /repo/ch01/php8_mixed_type.php
    declare(strict_types=1);
    class High {
        const LOG_FILE = __DIR__ . '/../data/test.log';
        protected static function logVar(object $var) {
            $item = date('Y-m-d') . ':'
                . var_export($var, TRUE);
            return error_log($item, 3, self::LOG_FILE);
        }
    }
  2. 接下来,我们定义一个扩展 HighLow 类,如下:

    class Low extends High {
        public static function logVar(mixed $var) {
            $item = date('Y-m-d') . ':'
                . var_export($var, TRUE);
            return error_log($item, 3, self::LOG_FILE);
        }
    }

    请注意,在 Low 类中,logVar() 方法的数据类型已扩展为混合类型。

  3. 最后,我们创建一个 Low 实例,并使用测试数据执行它。从以下代码片段显示的结果可以看出,一切运行正常:

    if (file_exists(High::LOG_FILE)) unlink(High::LOG_FILE)
    $test = [
        'array' => range('A', 'F'),
        'func' => function () { return __CLASS__; },
        'anon' => new class () {
            public function __invoke() {
                return __CLASS__;
            }
        },
    ];
    foreach ($test as $item) Low::logVar($item);
    readfile(High::LOG_FILE);

下面是前面例子的输出结果:

2020-10-15:array (
0 => 'A',
1 => 'B',
2 => 'C',
3 => 'D',
4 => 'E',
5 => 'F',
)2020-10-15:Closure::__set_state(array(
))2020-10-15:class@anonymous/repo/ch01/php8_mixed_type.
php:28$1::__set_state(array())

前面的代码块记录了各种不同的数据类型,然后显示了日志文件的内容。在此过程中,我们可以看到在 PHP 8 中,当子类覆盖父类方法并用混合数据类型代替限制性更强的数据类型(如 object)时,并不存在继承问题。

接下来,我们来看看类型属性的使用。

最佳实践:在定义函数或方法时为所有参数分配特定的数据类型。如果可以接受几种不同的数据类型,请定义联合类型。否则,如果这些都不适用,则退回到混合类型。

有关联合类型的信息,请参阅此文档页面: https://wiki.php.net/rfc/union_types_v2

有关混合类型的更多信息,请查看此处: https://wiki.php.net/rfc/mixed_type_v2