2024 日本 PHP 研討會

類型宣告

類型宣告可以添加到函式引數、返回值,從 PHP 7.4.0 開始,類別屬性,以及從 PHP 8.3.0 開始,類別常數。它們確保在呼叫時值是指定的類型,否則會拋出 TypeError

PHP 支援的每一種類型,除了 資源 之外,都可以在使用者端類型宣告中使用。此頁面包含不同類型可用性的變更日誌,以及在類型宣告中使用它們的文件。

注意:

當一個類別實作介面方法或重新實作一個已由父類別定義的方法時,它必須與前述定義相容。如果一個方法遵循變異數規則,則它是相容的。

更新日誌

版本 說明
8.3.0 已新增類別、介面、Trait 和列舉常數型別的支援。
8.2.0 已新增對DNF(Disjunctive Normal Form,析取正規型)型別的支援。
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 已新增對可空型別的支援。

原子型別使用注意事項

原子型別的行為很直接,但有一些細微的注意事項,將在本節中說明。

純量型別

警告

不支援純量型別 (bool, int, float, string) 的名稱別名。它們會被視為類別或介面名稱。例如,使用 boolean 作為型別宣告將要求值是類別或介面 booleaninstanceof(實例),而不是 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 {}
?>

可呼叫型別

此型別不能用作類別屬性型別宣告。

注意事項 無法指定函式的簽章 (signature)。

傳址參數的類型宣告

如果傳址參數具有類型宣告,變數的類型*僅*會在函式進入時,也就是呼叫開始時檢查,而不會在函式返回時檢查。這意味著函式可以更改變數參考的類型。

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

可接受 null 值的類型語法糖

可以在單一基本類型宣告前加上問號 (?) 來標記其可接受 null 值。因此 ?TT|null 是相同的。

注意事項 此語法自 PHP 7.1.0 起開始支援,早於廣義聯集類型的支援。

注意:

也可以透過將 null 設為預設值來實現可接受 null 值的參數。不建議這樣做,因為如果在子類別中更改了預設值,則會引發類型相容性違規,因為需要將 null 類型添加到類型宣告中。

範例 #2 舊的使參數可接受 null 值的方法

<?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 (Disjunctive Normal Form,析取正規形式) 類型:
    • 如果使用了更泛用的類型,則更具限制性的類型是多餘的。
    • 使用兩個相同的交集類型。

注意 這並不保證類型是「最小」的,因為這樣做需要載入所有使用的類別類型。

例如,如果 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 可為 Null 的引數類型宣告

<?php
class C {}

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

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

以上範例會輸出

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

範例 #8 可為 Null 的回傳值類型宣告

<?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 會將錯誤類型的值強制轉換為預期的純量類型宣告。例如,一個函式如果收到一個預期為 字串 的參數,卻給予一個 整數,則會得到一個 字串 類型的變數。

可以針對每個檔案啟用嚴格模式。在嚴格模式下,只會接受與類型宣告完全相符的值,否則會拋出 TypeError。此規則的唯一例外是 整數 值會通過 浮點數 類型宣告。

警告

從內部函式呼叫的函式不受 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 新增了展開運算子 (...),用於將陣列解包以作為函式參數。PHP 7.0 新增了純量型別提示。後續版本的 PHP further 改善了型別系統。有了這些新增和改進,就可以對型別陣列提供良好的支援。

<?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 jemals 原生支援類型化陣列,`doSomething` 的方法也更容易移除。這兩種方法都只適用於輸入參數。陣列返回值類型檢查需要在呼叫端進行。

如果未啟用 `strict_types`,可能需要從類型檢查函式返回強制轉換的純量值(例如,浮點數和字串變為整數)以確保正確的類型。
crash
3 年前
文件中缺少相關資訊,說明當介面的方法回傳類型定義為 `mixed` 時,可以更改介面中定義的方法的回傳類型。

來自 RFC

「`mixed` 回傳類型可以在子類別中縮小,因為這是共變的,並且在 LSP 中是允許的。」(https://wiki.php.net/rfc/mixed_type_v2)

這意味著以下程式碼在 PHP 8.0 中是有效的

<?php

介面 ITest
{
public function
apfel(): mixed; // 從 8.0 版開始有效
}

類別
Test 實作 ITest
{
public function
apfel(): array // 更明確的類型
{
return [];
}
}

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

您可以在這裡看到結果: https://3v4l.org/PXDB6
To Top