类型声明

类型声明可以添加到函数参数、返回值以及从 PHP 7.4.0 开始的类属性。它们确保在调用时值是指定类型,否则将抛出 TypeError

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

注意:

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

变更日志

版本 描述
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_NOTICE只有变量引用应该通过引用返回.

<?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 会尽可能地将错误类型的的值强制转换为预期的标量类型声明。例如,如果将一个 int 传递给一个期望 string 的参数的函数,则该函数将获得一个类型为 string 的变量。

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

警告

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

要启用严格模式,请使用带 strict_types 声明的 declare 语句。

注意:

严格类型适用于从启用严格类型的文件中内部进行的函数调用,而不是在该文件中声明的函数。如果一个未启用严格类型的文件调用了在启用严格类型的文件中定义的函数,则调用者的偏好(强制类型)将得到尊重,并且值将被强制转换。

注意:

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

示例 #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 只接受一个数组。doSomething 的方法也更容易去除,如果 PHP 以后添加了原生类型数组支持。这两种方法只适用于输入参数。数组返回值类型检查需要在调用站点进行。

如果未启用 strict_types,则可能需要从类型检查函数中返回强制转换后的标量值(例如,浮点数和字符串变为整数)以确保正确的类型。
crash
2 年前
文档缺少有关可以更改接口中定义的方法的返回类型的信息,而接口的方法返回类型定义为`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