類(lèi)型聲明

類(lèi)型聲明可以用于函數(shù)的參數(shù)、返回值,PHP 7.4.0 起還可以用于類(lèi)的屬性,來(lái)顯性的指定需要的類(lèi)型,如果預(yù)期類(lèi)型在調(diào)用時(shí)不匹配,則會(huì)拋出一個(gè) TypeError 異常。

注意:

當(dāng)子類(lèi)覆蓋父方法時(shí),子類(lèi)的方法必須匹配父類(lèi)的類(lèi)型聲明。如果父類(lèi)沒(méi)有定義返回類(lèi)型,那么子方法可以指定自己的返回類(lèi)型。

單一類(lèi)型

類(lèi)型 說(shuō)明 版本
類(lèi)/接口 名稱 值必須為指定類(lèi)和接口的實(shí)例化對(duì)象 instanceof  
self 值必須是用于類(lèi)型聲明相同類(lèi)的 instanceof 。 僅可在類(lèi)中使用。  
parent 值必須是用于類(lèi)型聲明父級(jí)類(lèi)的 instanceof , 僅可在類(lèi)中使用。  
array 值必須為 array。  
callable 值必定是一個(gè)有效的 callable。 不能用于類(lèi)屬性的類(lèi)型聲明。  
bool 值必須為一個(gè)布爾值。  
float 值必須為一個(gè)浮點(diǎn)數(shù)字。  
int 值必須為一個(gè)整型數(shù)字。  
string 值必須為一個(gè) string。  
iterable 值必須為 arrayinstanceof Traversable PHP 7.1.0
object 值必須為object。 PHP 7.2.0
mixed 值可以為任何類(lèi)型。 PHP 8.0.0
警告

不支持上述標(biāo)量類(lèi)型的別名。相反,它們被視為類(lèi)或接口名。例如,使用 boolean 作為類(lèi)型聲明,將要求值是一個(gè) instanceof 類(lèi)或接口 boolean,而不能是類(lèi)型 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

mixed

mixed 等同于 聯(lián)合類(lèi)型 object|resource|array|string|int|float|bool|null。PHP 8.0.0 起可用。

范例

示例 #1 在類(lèi)中使用類(lèi)型聲明

<?php
class {}
class 
extends {}

// 它沒(méi)有 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

示例 #2 在接口中使用類(lèi)型聲明

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

// 它沒(méi)有 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

示例 #3 返回類(lèi)型聲明

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

// 注意必須返回一個(gè) float。
var_dump(sum(12));
?>

以上例程會(huì)輸出:

float(3)

示例 #4 返回一個(gè)對(duì)象

<?php
class {}

function 
getC(): {
    return new 
C;
}

var_dump(getC());
?>

以上例程會(huì)輸出:

object(C)#1 (0) {
}

允許為空的(Nullable)類(lèi)型

自 PHP 7.1.0 起,類(lèi)型聲明允許前置一個(gè)問(wèn)號(hào) (?) 用來(lái)聲明這個(gè)值允許為指定類(lèi)型,或者為 null

示例 #5 定義可空(Nullable)的參數(shù)類(lèi)型

<?php
class {}

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

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

以上例程會(huì)輸出:

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

示例 #6 定義可空(Nullable)的返回類(lèi)型

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

注意:

可以通過(guò)設(shè)置參數(shù)的默認(rèn)值為 null 來(lái)實(shí)現(xiàn)允許為空的參數(shù)。不建議這樣做,因?yàn)橛绊懙筋?lèi)的繼承調(diào)用。

示例 #7 舊版本中實(shí)現(xiàn)允許為空參數(shù)的示例

<?php
class {}

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

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

以上例程會(huì)輸出:

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

復(fù)合類(lèi)型

還可以組合簡(jiǎn)單類(lèi)型為復(fù)合類(lèi)型。 PHP 支持以下方式復(fù)合類(lèi)型:

  • 簡(jiǎn)單類(lèi)型聯(lián)合。PHP 8.0.0 起可用。
  • class-type(接口、類(lèi)名)類(lèi)型交集。PHP 8.1.0 起可用。
警告

無(wú)法復(fù)合交集類(lèi)型與聯(lián)合類(lèi)型。

聯(lián)合類(lèi)型

聯(lián)合類(lèi)型接受多個(gè)不同的簡(jiǎn)單類(lèi)型做為參數(shù)。聲明聯(lián)合類(lèi)型的語(yǔ)法為 T1|T2|...。聯(lián)合類(lèi)型自 PHP 8.0.0 起可用。

允許為空的聯(lián)合類(lèi)型

null 類(lèi)型允許在聯(lián)合類(lèi)型中使用,例如 T1|T2|null 代表接受一個(gè)空值為參數(shù)。已經(jīng)存在的 ?T 語(yǔ)法可以視為以下聯(lián)合類(lèi)型的一個(gè)簡(jiǎn)寫(xiě) T|null。

警告

null 不能作為一個(gè)獨(dú)立的類(lèi)型使用。

false 偽類(lèi)型

通過(guò)聯(lián)合類(lèi)型支持字面類(lèi)型(Literal Type)false, 出于歷史原因,很多內(nèi)部函數(shù)在失敗時(shí)返回了 false 而不是 null。 這類(lèi)函數(shù)的典型例子是 strpos()。

警告

false 不能單獨(dú)作為類(lèi)型使用(包括可空 nullable 類(lèi)型)。 因此,不可以用 falsefalse|null、 ?false。

警告

true 字面類(lèi)型不存在

交集類(lèi)型

只要能滿足 class-type 的值,都可以在交集類(lèi)型聲明中使用,并且可使用多個(gè)值。 交集類(lèi)型用 T1&T2&... 這樣的語(yǔ)法指定。 交集類(lèi)型從 PHP 8.1.0 可以使用。

重復(fù)冗余的類(lèi)型

為了能在復(fù)合類(lèi)型聲明中暴露簡(jiǎn)單的 bug,不需要加載 class 就可以在編譯時(shí)讓重復(fù)冗余的類(lèi)型產(chǎn)生錯(cuò)誤。 包含:

  • 解析出來(lái)的類(lèi)型只能出現(xiàn)一次。例如這樣的類(lèi)型 int|string|INTCountable&Traversable&COUNTABLE 會(huì)導(dǎo)致錯(cuò)誤。
  • 使用 mixed 會(huì)導(dǎo)致錯(cuò)誤。
  • 對(duì)于聯(lián)合類(lèi)型:
    • 使用了 bool 時(shí)就不能再附帶使用 false。
    • 使用了 object 時(shí)就不能再附帶使用 class 類(lèi)型。
    • 使用了 iterable 時(shí),array、 Traversable 都不能再附帶使用。
  • 對(duì)于交集類(lèi)型:
    • 使用 class-type 以外的類(lèi)型會(huì)導(dǎo)致錯(cuò)誤。
    • 使用 self、parent、 static 都會(huì)導(dǎo)致錯(cuò)誤。

注意: 不過(guò)它不能確保類(lèi)型最小化,因?yàn)橐_(dá)到這樣的效果,還要加載使用類(lèi)型的 class。

例如,假設(shè) AB 都是一個(gè)類(lèi)的別名, 而 A|B 仍然是有效的,哪怕它可以被簡(jiǎn)化為 AB。 同樣的,如果 B extends A {},那 A|B 仍然是有效的聯(lián)合類(lèi)型,盡管它可以被簡(jiǎn)化為 A。

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

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

class_alias('X''Y');
function 
foo(): X|{} // 允許 (運(yùn)行時(shí)才能知道重復(fù)性)
function foo(): X&{} // 允許 (運(yùn)行時(shí)才能知道重復(fù)性)
?>

僅僅返回類(lèi)型

void

void 是一個(gè)返回類(lèi)型,用于標(biāo)識(shí)函數(shù)沒(méi)有返回值。 它不能是聯(lián)合類(lèi)型的一部分。 PHP 7.1.0 起可用。

注意:

從 PHP 8.1.0 起棄用引用返回 void 的函數(shù), 因?yàn)檫@樣的函數(shù)是矛盾的。 之前,它在調(diào)用時(shí)已經(jīng)發(fā)出如下 E_NOTICEOnly variable references should be returned by reference。

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

never

never 是一種表示沒(méi)有返回的返回類(lèi)型。這意味著它可能是調(diào)用 exit(), 拋出異?;蛘呤且粋€(gè)無(wú)限循環(huán)。所以,它不能作為聯(lián)合類(lèi)型的一部分。PHP 8.1.0 起可用。

在類(lèi)型理論用于中,never 是底層類(lèi)型。 意味著它是其它所有類(lèi)型的子類(lèi)型,并且可以在繼承期間替換任何其他返回類(lèi)型。

static

它的值必須是一個(gè) class 的 instanceof,該 class 是調(diào)用方法所在的同一個(gè)類(lèi)。 PHP 8.0.0 起有效。

嚴(yán)格類(lèi)型

默認(rèn)如果可能,PHP 會(huì)強(qiáng)制轉(zhuǎn)化不合適的類(lèi)型為想要的標(biāo)量類(lèi)型。 比如,參數(shù)想要 string,傳入的是 int, 則會(huì)獲取 string 類(lèi)型的變量。

可以按文件開(kāi)啟嚴(yán)格模式。 在嚴(yán)格模式下,只能接受完全匹配的類(lèi)型,否則會(huì)拋出 TypeError。 唯一的例外是 int 值也可以傳入聲明為 float 的類(lèi)型。

警告

通過(guò)內(nèi)部函數(shù)調(diào)用函數(shù)時(shí),不會(huì)受 strict_types 聲明影響。

要開(kāi)啟嚴(yán)格模式,使用 declare 開(kāi)啟 strict_types

注意:

文件開(kāi)啟嚴(yán)格類(lèi)型后的內(nèi)部調(diào)用函數(shù)將應(yīng)用嚴(yán)格類(lèi)型, 而不是在聲明函數(shù)的文件內(nèi)開(kāi)啟。 如果文件沒(méi)有聲明開(kāi)啟嚴(yán)格類(lèi)型,而被調(diào)用的函數(shù)所在文件有嚴(yán)格類(lèi)型聲明, 那將遵循調(diào)用者的設(shè)置(開(kāi)啟類(lèi)型強(qiáng)制轉(zhuǎn)化), 值也會(huì)強(qiáng)制轉(zhuǎn)化。

注意:

只有為標(biāo)量類(lèi)型的聲明開(kāi)啟嚴(yán)格類(lèi)型。

示例 #8 參數(shù)值的嚴(yán)格類(lèi)型

<?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

示例 #9 參數(shù)值的類(lèi)型強(qiáng)制轉(zhuǎn)化

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

var_dump(sum(12));

// 以下會(huì)強(qiáng)制轉(zhuǎn)化為整型,注意以下內(nèi)容輸出!
var_dump(sum(1.52.5));
?>

以上例程會(huì)輸出:

int(3)
int(3)

示例 #10 返回值的嚴(yán)格類(lèi)型

<?php
declare(strict_types=1);

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

var_dump(sum(12));
var_dump(sum(12.5));
?>

以上例程會(huì)輸出:

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

聯(lián)合類(lèi)型的內(nèi)部隱式強(qiáng)制轉(zhuǎn)化

沒(méi)有開(kāi)啟 strict_types 時(shí),標(biāo)量類(lèi)型可能會(huì)限制內(nèi)部隱式類(lèi)型轉(zhuǎn)化。 如果值的類(lèi)型不是聯(lián)合類(lèi)型中的一部分,則目標(biāo)類(lèi)型會(huì)按以下順序:

  1. int
  2. float
  3. string
  4. bool
如果類(lèi)型出現(xiàn)在組合中,值可以按 PHP 現(xiàn)有的類(lèi)型語(yǔ)義檢測(cè)進(jìn)行內(nèi)部隱式強(qiáng)制轉(zhuǎn)化,則會(huì)選擇該類(lèi)型。 否則會(huì)嘗試下一個(gè)類(lèi)型。

警告

有一個(gè)例外:當(dāng)值是字符串,而 int 與 float 同時(shí)在組合中,將按現(xiàn)有的“數(shù)字字符串”檢測(cè)語(yǔ)義,識(shí)別首選的類(lèi)型。 例如,"42" 會(huì)選擇 int 類(lèi)型, 而 "42.0" 會(huì)選擇 float 類(lèi)型。

注意:

沒(méi)有出現(xiàn)在上面列表中的類(lèi)型則不是有效的內(nèi)部隱式轉(zhuǎn)化目標(biāo)。 尤其是不會(huì)出現(xiàn)內(nèi)部隱式轉(zhuǎn)化 nullfalse 類(lèi)型。

示例 #11 類(lèi)型強(qiáng)制轉(zhuǎn)換為聯(lián)合類(lèi)型的例子

<?php
// int|string
42    --> 42          // 類(lèi)型完全匹配
"42"  --> "42"        // 類(lèi)型完全匹配
new ObjectWithToString --> "__toString() 的結(jié)果"
                      
// object 不兼容 int,降級(jí)到 string
42.0  --> 42          // float 與 int 兼容
42.1  --> 42          // float 與 int 兼容
1e100 --> "1.0E+100"  // float 比 int 大太多了,降級(jí)到 string
INF   --> "INF"       // float 比 int 大太多了,降級(jí)到 string
true  --> 1           // bool 與 int 兼容
[]    --> TypeError   // array 不兼容 int 或 string

// int|float|bool
"45"    --> 45        // int 的數(shù)字字符串
"45.0"  --> 45.0      // float 的數(shù)字字符串

"45X"   --> true      // 不是數(shù)字字符串,降級(jí)到 bool
""      --> false     // 不是數(shù)字字符串,降級(jí)到 bool
"X"     --> true      // 不是數(shù)字字符串,降級(jí)到 bool
[]      --> TypeError // array 不兼容 int、float、bool
?>

其他

示例 #12 傳引用參數(shù)的類(lèi)型

僅僅會(huì)在函數(shù)入口檢查傳引用的參數(shù)類(lèi)型,而不是在函數(shù)返回時(shí)檢查。 所以函數(shù)返回時(shí),參數(shù)類(lèi)型可能會(huì)發(fā)生變化。

<?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

示例 #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