此 RFC 为 PHP 添加泛型类型语法。类、接口、trait、函数、方法、闭包和箭头函数都可以声明类型参数;这些参数支持边界(bound)、默认值和协变/逆变标记;在使用的位置和调用位置可以通过 turbofish(::<...>)提供类型参数。
<?php
final readonly class Pair<+L, +R> {
publicfunction __construct(
public L $left,
public R $right,
) {}
publicfunction swap(): Pair<R, L> {
returnnew Pair($this->right, $this->left);
}
}
final readonly class Box<+T> {
publicfunction __construct(
public T $value,
) {}
publicfunction map<U>(callable $fn): Box<U> {
returnnew Box(($fn)($this->value));
}
publicfunction zip<O>(O $value): Box<Pair<T, O>> {
returnnew Box(new Pair($this->value, $value));
}
}
function identity<T>(T $value): T {
return $value;
}
$greeting = new Box::<string>("hello, world");
$paired = $greeting->zip::<int>(42);
$swapped = $paired->value->swap();
$result = identity::<Pair<int, string>>($swapped);
var_dump($result->left); // int(42)
var_dump($result->right); // string(12) "hello, world"
本提案中的泛型是绑定擦除(bound-erased)的:在运行时,Box<int> 和 Box<string> 是同一个类,每个类型参数都被替换为其声明的边界(无边界时为 mixed),引擎看到的是普通的 PHP 类型。强制检查分为三层:
new、属性构造)验证参数数量和边界。后备属性存储、属性钩子签名和 trait 属性会强制使用替换后的类型。新的 Reflection API 会暴露擦除前的形式,因此静态分析工具可以直接从引擎读取泛型信息,而非依赖 PHPDoc 约定。不使用泛型的代码会编译成与今天完全相同的字节码。
RFC 其余部分会讲解什么是泛型、PHP 为什么需要它们、塑造此设计的先例、完整提案、设计理由、实现,以及未来可叠加的运行时模型。
泛型之于类型,就像函数之于值。
函数对值进行抽象:sum(a, b) 从两个输入计算结果,无论 a 和 b 是 1 和 2 还是 100 和 200,函数体都适用。泛型对类型进行抽象:一个泛型 Stack 存放某种类型 T 的元素,无论 T 是 int、string 还是 User,同一个类体都适用。
没有泛型时,只有两种不理想的选择:
IntStack、StringStack、UserStack)。实现几乎完全相同,修复 bug 时必须在每份拷贝中修改。mixed 的单一数据结构。它对所有人可用,但每次从中取出值时都丢失了类型信息,必须信任生产者或重新检查。泛型类型会携带类型信息。声明 class Stack<T> 引入了一个类型参数 T,代表“元素类型,无论它是什么”。使用 Stack<int> 或 Stack<User> 则为该参数提供类型实参(type argument)。类体只需写一次,就能适用于每种 T 的选择;静态分析器和语言本身可以跟踪参数与流经它的值之间的关系。
人们已经在 PHP 中使用泛型了——只是写在注释里。
当今 PHP 生态中的每个主要框架、ORM、测试库和标准库项目,都在 PHPDoc 中携带泛型类型信息,由静态分析工具解析和验证。这些信息是真实的,关系是关键的,当 docblock 与实现脱节时,代码库会以微妙方式出错。RFC 详细列举了 Laravel、Symfony、PSL、Doctrine、Tempest、API Platform、PHPUnit 等项目的实际使用案例。
这些项目独立到达了相同结论:PHP 需要泛型。它们还实际采用了几乎相同的语法:@template T(可选 of X 表示边界)、使用位置的 <T, U>、继承转发标签(如 @extends、@implements 等)。
这种收敛是真实的、生态级的,但它没有经过标准流程。没有 @template 的规范,没有治理,没有共享测试套件。依赖的是多个静态分析工具(Mago、PHPStan、Psalm、Phan 及 IDE 中的 PHPDoc 解析器)在过去十年相互观察并相互支持的结果。
这种收敛是脆弱的。本 RFC 将泛型语法带入语言核心,让静态分析工具直接从引擎和 Reflection 获取信息,而非依赖脆弱的 PHPDoc 约定。
class Box<T> {}、function id<T>(T $x): T 等。T : SomeType)、默认值(K = string)、协变/逆变(+T、-T)。mixed。支持复杂类型、递归引用(嵌套形式)、同列表内相互引用。self<T>、static<T> 等。array 和 iterable 的泛型参数暂不支持(留给后续 RFC)。new Box::<User>()、filter::<User>($data) 等。运行时检查参数数量和边界。mixed)。参数化 LSP 在链接时进行签名替换。为什么选择绑定擦除? 它在 PHP 当前类型检查模型、生产使用记录(Java、Kotlin 等语言的擦除泛型长期成功)、性能和向前兼容性(未来可叠加重ified 或单态化)之间取得了良好平衡。运行时开销极低,不使用泛型的代码零影响。
其他设计决策包括:使用 <...> 作为类型参数、使用 ::<...>(turbofish)调用、使用 : 表示边界、使用 +/- 表示协变/逆变、使用 = 表示默认值等。
RFC:https://wiki.php.net/rfc/bound_erased_generic_types
PR:https://github.com/php/php-src/pull/21969