PHP Conference Japan 2024

类型声明

类型声明可以添加到函数参数、返回值,从 PHP 7.4.0 开始,还可以添加到类属性,从 PHP 8.3.0 开始,还可以添加到类常量。它们确保在调用时值具有指定的类型,否则会抛出 TypeError 异常。

除了 resource 之外,PHP 支持的每种类型都可以在用户态类型声明中使用。此页面包含不同类型可用性的变更日志以及关于在类型声明中使用它们的文档。

注意:

当一个类实现接口方法或重新实现父类已定义的方法时,它必须与上述定义兼容。如果一个方法遵循 方差规则,则该方法是兼容的。

变更日志

版本 描述
8.3.0 已添加对类、接口、特性和枚举常量类型的支持。
8.2.0 已添加对 DNF 类型的支持。
8.2.0 已添加对字面量类型 true 的支持。
8.2.0 现在可以单独使用 nullfalse 类型。
8.1.0 已添加对交集类型的支持。
8.1.0 void 函数返回引用现在已弃用。
8.1.0 已添加对仅返回类型 never 的支持。
8.0.0 已添加对 mixed 的支持。
8.0.0 已添加对仅返回类型 static 的支持。
8.0.0 已添加对联合类型的支持。
7.4.0 已添加对类属性类型的支持。
7.2.0 已添加对 object 的支持。
7.1.0 已添加对 iterable 的支持。
7.1.0 已添加对 void 的支持。
7.1.0 已添加对可空类型的支持。

原子类型用法说明

原子类型具有直接的行为,但也有一些细微的注意事项,在本节中进行了描述。

标量类型

警告

标量类型的名称别名(boolintfloatstring)不受支持。相反,它们被视为类或接口名称。例如,使用 boolean 作为类型声明将要求该值是 instanceof 类或接口 boolean 的实例,而不是 bool 类型。

<?php
function test(boolean $param) {}
test(true);
?>

上述示例在 PHP 8 中的输出

Warning: "boolean" will be interpreted as a class name. Did you mean "bool"? Write "\boolean" to suppress this warning in /in/9YrUX on line 2

Fatal error: Uncaught TypeError: test(): Argument #1 ($param) must be of type boolean, bool given, called in - on line 3 and defined in -:2
Stack trace:
#0 -(3): test(true)
#1 {main}
  thrown in - on line 2

void

注意:

从 PHP 8.1.0 开始,从 void 函数返回引用已弃用,因为这样的函数是矛盾的。以前,调用时它已经发出以下 E_NOTICEOnly variable references should be returned by reference

<?php
function &test(): void {}
?>

可调用类型

此类型不能用作类属性类型声明。

注意无法指定函数的签名。

按引用传递参数的类型声明

如果按引用传递的参数具有类型声明,则仅在函数入口处(调用开始时)检查变量的类型,而不在函数返回时检查。这意味着函数可以更改变量引用的类型。

示例 #1 类型化的按引用传递参数

<?php
function array_baz(array &$param)
{
$param = 1;
}
$var = [];
array_baz($var);
var_dump($var);
array_baz($var);
?>

上述示例将输出类似于以下内容

int(1)

Fatal error: Uncaught TypeError: array_baz(): Argument #1 ($param) must be of type array, int given, called in - on line 9 and defined in -:2
Stack trace:
#0 -(9): array_baz(1)
#1 {main}
  thrown in - on line 2

复合类型用法说明

复合类型声明受到一些限制,并在编译时执行冗余检查以防止简单的错误。

警告

在 PHP 8.2.0 之前,以及引入 DNF 类型之前,无法将交集类型与联合类型组合。

联合类型

警告

无法在联合类型中组合两个值类型 falsetrue。请改用 bool

警告

在 PHP 8.2.0 之前,由于 falsenull 不能用作独立类型,因此不允许仅包含这些类型的联合类型。这包括以下类型:falsefalse|null?false

可空类型语法糖

可以通过在类型前添加问号 (?) 来将单个基本类型声明标记为可空。因此 ?TT|null 是相同的。

注意此语法从 PHP 7.1.0 开始受支持,早于广义联合类型支持。

注意:

也可以通过使 null 成为默认值来实现可空参数。这不推荐,因为如果在子类中更改了默认值,则会引发类型兼容性冲突,因为需要将 null 类型添加到类型声明中。

示例 #2 使参数可空的老方法

<?php
class C {}

function
f(C $c = null) {
var_dump($c);
}

f(new C);
f(null);
?>

上述示例将输出

object(C)#1 (0) {
}
NULL

重复和冗余类型

为了捕获复合类型声明中的简单错误,可以在不执行类加载的情况下检测到的冗余类型将导致编译时错误。这包括

  • 每个已解析名称的类型只能出现一次。诸如int|string|INTCountable&Traversable&COUNTABLE之类的类型会导致错误。
  • 使用mixed 会导致错误。
  • 对于联合类型
  • 对于交集类型
    • 使用不是类类型的类型会导致错误。
    • 使用selfparentstatic中的任何一个都会导致错误。
  • 对于DNF类型
    • 如果使用了更通用的类型,则更严格的类型是冗余的。
    • 使用两个相同的交集类型。

注意 这并不能保证类型是“最小”的,因为这样做需要加载所有使用的类类型。

例如,如果AB是类别名,则A|B仍然是一个合法的联合类型,即使它可以简化为AB。类似地,如果类B extends A {},则A|B也是一个合法的联合类型,即使它可以简化为仅A

<?php
function foo(): int|INT {} // 不允许
function foo(): bool|false {} // 不允许
function foo(): int&Traversable {} // 不允许
function foo(): self&Traversable {} // 不允许

use A as B;
function
foo(): A|B {} // 不允许(“use”是名称解析的一部分)
function foo(): A&B {} // 不允许(“use”是名称解析的一部分)

class_alias('X', 'Y');
function
foo(): X|Y {} // 允许(冗余只有在运行时才知道)
function foo(): X&Y {} // 允许(冗余只有在运行时才知道)
?>

示例

示例 #3 基本类类型声明

<?php
class C {}
class
D extends C {}

// 这不扩展 C。
class E {}

function
f(C $c) {
echo
get_class($c)."\n";
}

f(new C);
f(new D);
f(new E);
?>

上述示例在 PHP 8 中的输出

C
D

Fatal error: Uncaught TypeError: f(): Argument #1 ($c) must be of type C, E given, called in /in/gLonb on line 14 and defined in /in/gLonb:8
Stack trace:
#0 -(14): f(Object(E))
#1 {main}
  thrown in - on line 8

示例 #4 基本接口类型声明

<?php
interface I { public function f(); }
class
C implements I { public function f() {} }

// 这不实现 I。
class E {}

function
f(I $i) {
echo
get_class($i)."\n";
}

f(new C);
f(new E);
?>

上述示例在 PHP 8 中的输出

C

Fatal error: Uncaught TypeError: f(): Argument #1 ($i) must be of type I, E given, called in - on line 13 and defined in -:8
Stack trace:
#0 -(13): f(Object(E))
#1 {main}
  thrown in - on line 8

示例 #5 基本返回类型声明

<?php
function sum($a, $b): float {
return
$a + $b;
}

// 注意将返回浮点数。
var_dump(sum(1, 2));
?>

上述示例将输出

float(3)

示例 #6 返回对象

<?php
class C {}

function
getC(): C {
return new
C;
}

var_dump(getC());
?>

上述示例将输出

object(C)#1 (0) {
}

示例 #7 可空参数类型声明

<?php
class C {}

function
f(?C $c) {
var_dump($c);
}

f(new C);
f(null);
?>

上述示例将输出

object(C)#1 (0) {
}
NULL

示例 #8 可空返回类型声明

<?php
function get_item(): ?string {
if (isset(
$_GET['item'])) {
return
$_GET['item'];
} else {
return
null;
}
}
?>

示例 #9 类属性类型声明

<?php
class User {
public static
string $foo = 'foo';

public
int $id;
public
string $username;

public function
__construct(int $id, string $username) {
$this->id = $id;
$this->username = $username;
}
}
?>

严格类型

默认情况下,如果可能,PHP 会将错误类型的数值强制转换为预期的标量类型声明。例如,如果为期望string类型的参数传递了int数值,则会得到string类型的变量。

可以在每个文件的基础上启用严格模式。在严格模式下,只接受与类型声明完全一致的数值,否则将抛出TypeError。此规则的唯一例外是int数值将通过float类型声明。

警告

内部函数中的函数调用不会受到strict_types声明的影响。

要启用严格模式,可以使用declare语句和strict_types声明。

注意:

严格类型适用于从启用了严格类型的文件中_内部_进行的函数调用,而不适用于在该文件中声明的函数。如果启用了严格类型的文件调用在启用了严格类型的文件中定义的函数,则将尊重调用者的首选项(强制类型转换),并且将强制转换该值。

注意:

严格类型仅针对标量类型声明定义。

示例 #10 参数值的严格类型

<?php
declare(strict_types=1);

function
sum(int $a, int $b) {
return
$a + $b;
}

var_dump(sum(1, 2));
var_dump(sum(1.5, 2.5));
?>

上述示例在 PHP 8 中的输出

int(3)

Fatal error: Uncaught TypeError: sum(): Argument #1 ($a) must be of type int, float given, called in - on line 9 and defined in -:4
Stack trace:
#0 -(9): sum(1.5, 2.5)
#1 {main}
  thrown in - on line 4

示例 #11 参数值的强制类型转换

<?php
function sum(int $a, int $b) {
return
$a + $b;
}

var_dump(sum(1, 2));

// 这些将被强制转换为整数:请注意下面的输出!
var_dump(sum(1.5, 2.5));
?>

上述示例将输出

int(3)
int(3)

示例 #12 返回值的严格类型

<?php
declare(strict_types=1);

function
sum($a, $b): int {
return
$a + $b;
}

var_dump(sum(1, 2));
var_dump(sum(1, 2.5));
?>

上述示例将输出

int(3)

Fatal error: Uncaught TypeError: sum(): Return value must be of type int, float returned in -:5
Stack trace:
#0 -(9): sum(1, 2.5)
#1 {main}
  thrown in - on line 5
添加笔记

用户贡献笔记 2 条笔记

toinenkayt (ta at ta) [iwonderr] gmail d
3 年前
在等待对类型化数组的原生支持时,这里有一些替代方法,可以通过滥用可变参数函数来确保数组的强类型。这些方法的性能对编写者来说是一个谜,因此对其进行基准测试的责任落在了读者身上。

PHP 5.6 添加了 splat 运算符 (...),用于解包用作函数参数的数组。PHP 7.0 添加了标量类型提示。更高版本的 PHP 进一步改进了类型系统。通过这些添加和改进,可以对类型化数组提供不错的支持。

<?php
declare (strict_types=1);

function
typeArrayNullInt(?int ...$arg): void {
}

function
doSomething(array $ints): void {
(function (?
int ...$arg) {})(...$ints);
// 或者,
(fn (?int ...$arg) => $arg)(...$ints);
// 或者为了避免使用太多闭包来占用内存
typeArrayNullInt(...$ints);

/* ... */
}

function
doSomethingElse(?int ...$ints): void {
/* ... */
}

$ints = [1,2,3,4,null];
doSomething ($ints);
doSomethingElse (...$ints);
?>

这两种方法都适用于所有类型声明。这里的关键思想是让函数在遇到类型违规时抛出运行时错误。在 `doSomethingElse` 中使用的方法更简洁,但它不允许在可变参数之后有任何其他参数。它还需要调用站点知道这种类型实现并解包数组。在 `doSomething` 中使用的方法比较混乱,但它不需要调用站点知道类型方法,因为解包是在函数内执行的。它也更清晰,因为 `doSomethingElse` 也可以接受 n 个单独的参数,而 `doSomething` 只接受一个数组。如果 PHP 以后添加了原生类型化数组支持,`doSomething` 的方法也更容易去除。这两种方法只适用于输入参数。数组返回值类型检查需要在调用站点进行。

如果未启用 `strict_types`,则可能需要从类型检查函数返回强制转换的标量值(例如,浮点数和字符串变为整数),以确保正确的类型。
crash
3 年前
文档缺少信息,即当接口的方法返回类型定义为 `mixed` 时,可以更改接口中定义的方法的返回类型。

来自 RFC

“混合返回类型可以在子类中缩小,因为这是协变的,并且在 LSP 中是允许的。” (https://wiki.php.net/rfc/mixed_type_v2)

这意味着以下代码在 PHP 8.0 中有效

<?php

interface ITest
{
public function
apfel(): mixed; // 自 8.0 起有效
}

class
Test implements ITest
{
public function
apfel(): array // 更明确
{
return [];
}
}

var_dump((new Test())->apfel());
?>

您可以在此处查看结果:https://3v4l.org/PXDB6
To Top