类型声明

类型声明可以用于函数的参数、返回值,PHP 7.4.0 起还可以用于类的属性,来显性的指定需要的类型,如果预期类型在调用时不匹配,则会抛出一个 TypeError 异常。

Note:

当子类覆盖父方法时,子类的方法必须匹配父类的类型声明。如果父类没有定义返回类型,那么子方法可以指定自己的返回类型。

单一类型

类型 说明 版本
类/接口 名称 值必须为指定类和接口的实例化对象 instanceof  
self 值必定是所在方法的类的一个 instanceof。 只能在类的内部使用。  
array 值必须为 array  
callable 值必定是一个有效的 callable。 不能用于类属性的类型声明。  
bool 值必须为一个布尔值。  
float 值必须为一个浮点数字。  
int 值必须为一个整型数字。  
string 值必须为一个 string  
iterable 值必须为 arrayinstanceof Traversable PHP 7.1.0
object 值必须为object PHP 7.2.0
mixed 值可以为任何类型。 PHP 8.0.0
Warning

不支持上述标量类型的别名。相反,它们被视为类或接口名。例如,使用 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

范例

Example #1 在类中使用类型声明

<?php
class {}
class 
extends {}

// 它没有 extend C。
class {}

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

Example #2 在接口中使用类型声明

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

// 它没有 implement I。
class {}

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

Example #3 返回类型声明

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

// 注意必须返回一个 float。
var_dump(sum(12));
?>

以上例程会输出:

float(3)

Example #4 返回一个对象

<?php
class {}

function 
getC(): {
    return new 
C;
}

var_dump(getC());
?>

以上例程会输出:

object(C)#1 (0) {
}

允许为空的(Nullable)类型

自 PHP 7.1.0 起,类型声明允许前置一个问号 (?) 用来声明这个值允许为指定类型,或者为 null

Example #5 定义可空(Nullable)的参数类型

<?php
class {}

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

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

以上例程会输出:

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

Example #6 定义可空(Nullable)的返回类型

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

Note:

在 PHP 7.1.0 之前版本中,可以通过设置参数的默认值为 null 来实现允许为空的参数。不建议这样做,因为影响到类的继承调用。

Example #7 旧版本中实现允许为空参数的示例

<?php
class {}

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

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

以上例程会输出:

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

联合类型

联合类型接受多个不同的类型做为参数。声明联合类型的语法为 T1|T2|...。联合类型自 PHP 8.0.0 起可用。

允许为空的联合类型

null 类型允许在联合类型中使用,例如 T1|T2|null 代表接受一个空值为参数。已经存在的 ?T 语法可以视为以下联合类型的一个简写 T|null

Caution

null 不能作为一个独立的类型使用。

false 伪类型

通过联合类型支持字面类型(Literal Type)false, 出于历史原因,很多内部函数在失败时返回了 false 而不是 null。 这类函数的典型例子是 strpos()

Caution

false 不能单独作为类型使用(包括可空 nullable 类型)。 因此,falsefalse|null?false 都是不可以用的。

Caution

true 字面类型不存在

重复冗余的类型

为了能在联合类型声明中暴露简单的 bug,不需要加载 class 就可以在编译时让重复冗余的类型产生错误。 包含:

  • 解析出来的类型只能出现一次。例如这样的类型 int|string|INT 会导致错误。
  • 使用了 bool 时就不能再附带使用 false
  • 使用了 object 时就不能再附带使用 class 类型。
  • 使用了 iterable 时,arrayTraversable 都不能再附带使用。

Note: 不过它不能确保类型最小化,因为要达到这样的效果,还要加载使用类型的 class。

例如,假设 AB 都是一个类的别名, 而 A|B 仍然是有效的,哪怕它可以被简化为 AB。 同样的,如果 B extends A {},那 A|B 仍然是有效的联合类型,尽管它可以被简化为 A

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

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

class_alias('X''Y');
function 
foo(): X|{} // 允许 (运行时才能知道重复性)
?>

仅仅返回类型

void

void 是一个返回类型,用于标识函数没有返回值。 它不能是联合类型的一部分。 PHP 7.1.0 起可用。

static

它的值必须是一个 class 的 instanceof,该 class 是调用方法所在的同一个类。 PHP 8.0.0 起有效。

严格类型

默认如果可能,PHP 会强制转化不合适的类型为想要的标量类型。 比如,参数想要 string,传入的是 int, 则会获取 string 类型的变量。

可以按文件开启严格模式。 在严格模式下,只能接受完全匹配的类型,否则会抛出 TypeError。 唯一的例外是 int 值也可以传入声明为 float 的类型。

Warning

通过内部函数调用函数时,不会受 strict_types 声明影响。

要开启严格模式,使用 declare 开启 strict_types

Note:

文件开启严格类型后的内部调用函数将应用严格类型, 而不是在声明函数的文件内开启。 如果文件没有声明开启严格类型,而被调用的函数所在文件有严格类型声明, 那将遵循调用者的设置(开启类型强制转化), 值也会强制转化。

Note:

只有为标量类型的声明开启严格类型。

Example #8 参数值的严格类型

<?php
declare(strict_types=1);

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

var_dump(sum(12));
var_dump(sum(1.52.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

Example #9 参数值的类型强制转化

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

var_dump(sum(12));

// 以下会强制转化为整型,注意以下内容输出!
var_dump(sum(1.52.5));
?>

以上例程会输出:

int(3)
int(3)

Example #10 返回值的严格类型

<?php
declare(strict_types=1);

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

var_dump(sum(12));
var_dump(sum(12.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

联合类型的内部隐式强制转化

没有开启 strict_types 时,标量类型可能会限制内部隐式类型转化。 如果值的类型不是联合类型中的一部分,则目标类型会按以下顺序:

  1. int
  2. float
  3. string
  4. bool
如果类型出现在组合中,值可以按 PHP 现有的类型语义检测进行内部隐式强制转化,则会选择该类型。 否则会尝试下一个类型。

Caution

有一个例外:当值是字符串,而 int 与 float 同时在组合中,将按现有的"数字字符串"检测语义,识别首选的类型。 例如,"42" 会选择 int 类型, 而 "42.0" 会选择 float 类型。

Note:

没有出现在上面列表中的类型则不是有效的内部隐式转化目标。 尤其是不会出现内部隐式转化 nullfalse 类型。

Example #11 类型强制转换为联合类型的例子

<?php
// int|string
42    --> 42          // 类型完全匹配
"42"  --> "42"        // 类型完全匹配
new ObjectWithToString --> "__toString() 的结果"
                      
// object 不兼容 int,降级到 string
42.0  --> 42          // float 与 int 兼容
42.1  --> 42          // float 与 int 兼容
1e100 --> "1.0E+100"  // float 比 int 大太多了,降级到 string
INF   --> "INF"       // float 比 int 大太多了,降级到 string
true  --> 1           // bool 与 int 兼容
[]    --> TypeError   // array 不兼容 int 或 string

// int|float|bool
"45"    --> 45        // int 的数字字符串
"45.0"  --> 45.0      // float 的数字字符串

"45X"   --> true      // 不是数字字符串,降级到 bool
""      --> false     // 不是数字字符串,降级到 bool
"X"     --> true      // 不是数字字符串,降级到 bool
[]      --> TypeError // array 不兼容 int、float、bool
?>

其他

Example #12 传引用参数的类型

仅仅会在函数入口检查传引用的参数类型,而不是在函数返回时检查。 所以函数返回时,参数类型可能会发生变化。

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

以上例程在 PHP 8 中的输出:

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

Example #13 捕获 TypeError

<?php
declare(strict_types=1);

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

try {
    
var_dump(sum(12));
    
var_dump(sum(1.52.5));
} catch (
TypeError $e) {
    echo 
'Error: '$e->getMessage();
}
?>

以上例程在 PHP 8 中的输出:

int(3)
Error: sum(): Argument #1 ($a) must be of type int, float given, called in - on line 10

User Contributed Notes

toinenkayt (ta at ta) [iwonderr] gmail d 24-Jun-2021 11:10
While waiting for native support for typed arrays, here are a couple of alternative ways to ensure strong typing of arrays by abusing variadic functions. The performance of these methods is a mystery to the writer and so the responsibility of benchmarking them falls unto the reader.

PHP 5.6 added the splat operator (...) which is used to unpack arrays to be used as function arguments. PHP 7.0 added scalar type hints. Latter versions of PHP have further improved the type system. With these additions and improvements, it is possible to have a decent support for typed arrays.

<?php
declare (strict_types=1);

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

function
doSomething(array $ints): void {
    (function (?
int ...$arg) {})(...$ints);
   
// Alternatively,
   
(fn (?int ...$arg) => $arg)(...$ints);
   
// Or to avoid cluttering memory with too many closures
   
typeArrayNullInt(...$ints);

   
/* ... */
}

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

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

Both methods work with all type declarations. The key idea here is to have the functions throw a runtime error if they encounter a typing violation. The typing method used in doSomethingElse is cleaner of the two but it disallows having any other parameters after the variadic parameter. It also requires the call site to be aware of this typing implementation and unpack the array. The method used in doSomething is messier but it does not require the call site to be aware of the typing method as the unpacking is performed within the function. It is also less ambiguous as the doSomethingElse would also accept n individual parameters where as doSomething only accepts an array. doSomething's method is also easier to strip away if native typed array support is ever added to PHP. Both of these methods only work for input parameters. An array return value type check would need to take place at the call site.

If strict_types is not enabled, it may be desirable to return the coerced scalar values from the type check function (e.g. floats and strings become integers) to ensure proper typing.
anisgazig at example dot com 26-Apr-2021 06:20
same data type and same value but first function declare as a argument type declaration and return int(7)
and second fucntion declare as a return type declaration but return int(8).

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

var_dump(argument_type_declaration(3.5,4.7));
//output:int(7)

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

var_dump(return_type_declaration(3.5,4.7));

//output:int(8)