探索新的数据类型
任何入门级 PHP 开发人员都要学习的一件事就是 PHP 有哪些可用的 数据类型 以及如何使用它们。基本数据类型包括 int
(整数)、float
、bool
(布尔)和 string
。复杂数据类型包括数组和对象。此外,还有 NULL
和 resource
等其他数据类型。本节将讨论 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。不过,有一些限制在大多数情况下是完全合理的。本表总结了比较重要的限制:
Disallowed | Not Allowed Example |
---|---|
联合类型中不能包含 void |
void|int |
混合型不能包含在联合类型中(详情见下文) |
mixed|int |
Nullable 类型不能包含在联合类型中 |
string|?array |
联合类型中不能同时包含 TRUE 和 FALSE。 |
TRUE|FALSE |
不允许冗余类型 |
int|float|int|array|iterable|Traversable |
从这个例外列表中可以看出,定义联合类型主要是一个常识问题。
最佳实践:在使用联合类型时,如果不执行严格的类型检查,类型强制(PHP 为满足函数的要求而在内部转换数据类型的过程)可能会造成问题。因此,最佳做法是在使用联合类型的文件顶部添加如下内容:
|
联合类型例子
让我们以本章中使用的 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);
}
在上例中,除了第一个参数可以接受 array
或 int
类型的属性外,在方法签名本身中,int|array
联合类型也清楚地表明了这一选择。
混合型
mixed
是 PHP 8 中引入的另一种新类型。与 union 类型不同,mixed
是一种实际的数据类型,代表类型的最终结合。它用来表示接受任何和所有的数据类型。从某种意义上说,PHP 已经有了这种功能:只需省略数据类型,它就是一个隐含的 mixed
类型!
您会在 PHP 文档中看到对混合类型的引用。PHP 8 将混合类型作为一种实际的数据类型,从而使这种表示形式正规化。 |
混合类型对继承的影响
由于 mixed 类型代表了 widening 的终极范例,因此当一个类从另一个类扩展时,可以用它来拓宽数据类型的定义。下面是一个使用混合类型的示例,说明了这一原理:
-
首先,我们使用限制性更强的
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); } }
-
接下来,我们定义一个扩展
High
的Low
类,如下: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()
方法的数据类型已扩展为混合类型。 -
最后,我们创建一个
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 。 |