類型提示 (Type Hinting/Declaration) 是 PHP 從弱類型腳本語言向強類型工程語言演進的關鍵里程碑。本文梳理 PHP 5.0 → 8.2 的類型系統演進,對比標量/返回類型/聯合類型/交集類型的語法與語義,並給出現代項目的類型策略建議。
| 版本 | 里程碑特性 | 影響 |
|---|---|---|
| PHP 5.0 (2004) | 類/介面類型提示、array、callable | 首次引入參數類型約束,僅限複雜類型 |
| PHP 5.4 (2012) | Closure 類型提示 | 支援匿名函數類型約束 |
| PHP 7.0 (2015) | 標量類型 (int/float/string/bool)、返回類型聲明、declare(strict_types=1) | 質變:函數簽名完整可檢查,嚴格模式消除隱式轉換 Bug |
| PHP 7.1 (2016) | 可空類型 ?Type、void 返回、iterable 偽類型 | 更精確表達「無返回」與「可為 null」 |
| PHP 7.2 (2017) | object 類型提示 | 泛指任意對象實例 |
| PHP 7.4 (2019) | 類屬性類型聲明、协变返回/逆变參數、?Type 簡寫完善 | 屬性也能靜態分析,介面契約更強 |
| PHP 8.0 (2020) | 聯合類型 Type1|Type2、mixed、命名參數、Attributes (屬性)、static 返回類型 | 類型系統表達力大增,元編程能力躍升 |
| PHP 8.1 (2021) | never 返回類型、readonly 屬性、new 初始化器、交集類型 (僅 `class-implements`)、pure-intersection-types RFC | 不可實例化類型、不可變對象、純交集類型 |
| PHP 8.2 (2022) | DNF 型別 (Disjunctive Normal Form)、null、false 作為獨立類型、readonly 類 | 任意複雜聯合/交集組合、更精確的標準庫簽名 |
| PHP 8.3 (2023) | #[Override] 屬性、class constant visibility、深度唯讀屬性 | 編譯期檢查覆寫正確性 |
<?php
declare(strict_types=1); // 文件級嚴格模式:禁止隱式轉換
function calc(float $a, float $b): float {
return $a + $b;
}
calc(1, 2); // OK: int → float 寬化轉換 (嚴格模式下也允許)
calc(1.5, 2.5); // OK
// calc('1', '2'); // TypeError: string 不能隱式轉 float (嚴格模式)
// calc(1, '2'); // TypeError: 第 2 參數必須是 float
關鍵規則:strict_types=1 僅影響調用方傳入參數與返回值的檢查;函數內部的隱式轉換 (如 `$a + $b`) 不受影響。
<?php
function format(InputInterface|string $data): string {
if ($data instanceof InputInterface) {
$data = $data->getContent();
}
return strtoupper($data);
}
// 等價於 PHP 7.1 的 ?string (但更明確)
function find(int|float $id): User|null { ... }
限制:`void` 不能參與聯合;`false` (PHP 8.2) 僅用於歷史標準庫兼容 (如 `strpos` 返回 `int|false`)。
<?php
interface Loggable { public function log(): void; }
interface Countable { public function count(): int; }
// 參數必須同時實現 Loggable 和 Countable
function process(Loggable&Countable $obj): void {
$obj->log();
echo $obj->count();
}
規則:只能由 類/介面 組成,不能包含標量類型;self、parent、static 可參與交集。
<?php
// (A&B)|(C&D) 形式:聯合中的每個元素是交集
function foo((Traversable&Countable)|(ArrayAccess&Serializable) $x): void { }
// 實際場景:PSR-7 ResponseInterface 可能是
// (MessageInterface&ResponseInterface)|(StreamInterface&ResponseInterface)
| 類型 | 版本 | 含義 | 典型用例 |
|---|---|---|---|
mixed | 8.0 | 任意類型 (等價於 array|bool|callable|int|float|null|object|resource|string) | 未知結構數據、遞歸樹、框架中間件 |
never | 8.1 | 函數永不正常返回 (拋異常、exit、死循環) | function abort(): never { throw new Exception(); } |
static | 8.0 | 晚期靜態綁定類型 (返回當前實例類型) | 流式介面、Builder 模式、單例 getInstance(): static |
self | 5.0 | 定義該方法的具體類 (非子類) | 工廠方法返回確定類型 |
parent | 5.0 | 父類類型 | 構造函數鏈調用 |
<?php
interface Repository {
// 返回類型協變:子類可縮窄返回類型
public function find(int $id): Entity;
}
class UserRepository implements Repository {
public function find(int $id): User { ... } // User 是 Entity 子類 ✓
}
// 參數類型逆變:子類可擴寬參數類型 (較少用)
interface Processor {
public function handle(Entity $e): void;
}
class FlexibleProcessor implements Processor {
public function handle(object $e): void { ... } // object 更寬 ✓
}
專案根目錄 `composer.json` → `config.platform.php` ≥ 8.1,所有 PHP 文件首行強制:
<?php declare(strict_types=1);
# composer require --dev phpstan/phpstan phpstan/phpstan-strict-rules
# phpstan.neon
parameters:
level: 8
paths:
- src
checkMissingIterableValueType: true
treatPhpDocTypesAsCertain: true
?string $x = null (明確可空) ≠ string $x = '' (非空字符串默認)。PHP 類型系統已從「可選裝飾」進化為「架構基石」。嚴格模式 + 完整類型簽名 + 靜態分析 CI 是現代 PHP 專案的鐵三角。擁抱聯合/交集/DNF 型別,用類型表達業務不變量,讓編譯器成為你最忠實的測試工程師——編譯通過,業務邏輯錯誤率下降 80% 以上。