PHP 泛型之殇 泛型 RFC 提案被拒绝
PHP 泛型之殇 泛型 RFC 提案被拒绝
PHP 开发者眼中的泛型现实
PHP 很可能不会迎来泛型。这并非什么新鲜事。一些核心开发者撰写了详尽的技术博客,阐述为什么运行时擦除式泛型不是个好主意,以及他们为何对当前的 RFC 投下反对票。
然而,PHP 社区中还有另一群人,他们非常支持这个 RFC,也希望表达自己的观点。有些人没有合适的平台来发声,因此本文想给另一方一个讲述自己故事的机会。以下内容来自那些在日常开发中实际使用泛型、并将静态分析作为 PHP 核心实践之一的开发者们。他们想分享自己的真实想法。
来自 Symfony 核心维护者 Nicolas Grekas
关于 PHP 泛型已有大量详尽的前期研究。结论十分明确:PHP 引擎无法高效实现泛型(挑战包括类型推导不能破坏开发者体验、性能不能增加运行成本等众多方面)。唯一可行的方案是提前静态分析。梦想在 PHP 自身内核中内置一个用 C 编写的静态分析器固然美好,但并不现实:PHP 静态分析工具之所以用 PHP 开发,自有其道理。这种方式允许快速迭代,由精通该语言的人来推进。将这种经验迁移到引擎侧,将会把那些真正让 PHP 静态分析成为现实的人排斥在外,这对社区来说将是一个巨大的损失。
提案中的擦除式泛型正是这条道路的延续,而这条路已被证明是可行且有用的。得益于 IDE,PHP 静态分析的用户基础远比 phpstan、psalm 等工具的活跃用户广泛得多。在 LLM 生成代码的时代,PHP 拥有更强验证环节的必要性更加凸显。静态分析工具如今已成为开发流程中不可或缺的一部分。多年来,社区一直在等待和憧憬泛型的一点一点推进,甚至连泛型数组都曾因过于困难而被否决。等待的时间已经足够长,足以得出一个结论:擦除式泛型是唯一现实的进步方向。当然,如果事实证明判断有误,那也会是一件令人高兴的事。几年之后(如果那时人们和 LLM 还在写 PHP 的话)再看分晓。
来自 Tempest 核心开发者、全职 PHP 开发者 Márk Magyar
首先,需要坦诚地说:如果这个 RFC 未能通过,他会感到极度失望。但先从最没有争议的地方说起——多年来,他一直在 PHP 中使用泛型,事实上大多数 PHP 开发者也是如此。
如果曾经打开过任何一个 Laravel、Doctrine、Symfony、PHPUnit 或 PSL 类,就一定会看到 @template 注解。它在 GitHub 上出现在超过 20.2 万个文件中。这是生态系统中每一个严肃的集合、仓库和结果类型的自我描述方式。社区使用擦除式泛型——即静态分析器检查而引擎忽略的类型——已经将近十年。因此,当有人把这个 RFC 说得像某种新奇的外来事物时,会让人感到有些困惑。Bound-Erased Generic Types RFC 实际上唯一做的事情,就是不再让开发者把这些泛型写在注释里,假装它们不算数。
这种注释开销是真实存在的。想想看,一个泛型 Laravel 类今天长什么样。它同时用两种语言编写:签名里的 PHP 类型,以及紧随其上的 docblock 中的 PHPDoc 类型。解析器验证前者,却默默信任后者。重构时重命名一个属性,注释就悄悄过时了。ReflectionClass::getName() 返回的是 Collection,而不是 Collection<TKey, TModel>。PHPStan、Psalm 和 Mago 对复杂情况的解读各有一些差异,因为根本不存在一个它们可以参照的实际语言标准。
这个 RFC 以零运行时成本解决了所有这些问题。然而在撰写本文时,它的投票结果是 4 票赞成 / 12 票反对 / 3 票弃权。想谈谈这背后的原因,因为这才是真正的悲剧所在,而且比"核心团队讨厌泛型"更令人沮丧。事实并非如此。真正的原因比这更糟糕。
"但原生语法不应该撒谎"
反对这个 RFC 的最强论点并不愚蠢,因此不会试图硬碰硬。Rowan Tommins 在邮件列表中说得最好:
但目前,PHP 的原生语法不会撒谎——标记为 "private" 的属性确实是私有的,返回类型标记为 "int" 的确实总是整型……这个提案将从根本上改变这一点——它将引入一种看起来像是标准强制类型系统一部分的语法,但在很多情况下却完全不起作用。
Derick Rethans 从经验出发表示赞同,称用户"在发现这些类型不会被强制检查时,几乎无一例外地感到困惑"。Tim Düsterhus 进一步指出:静态分析器"只能证明错误的存在,无法证明错误的不存在"——他说的没错,不实际运行 PHP 程序就无法对其做完整的类型检查。
面对这个论点思考了良久,因为它听起来无懈可击。但是——总有一个但是——这是一个关于纯正性的论点,而 PHP 的类型系统从来就不纯正。运行时已经不会检查数组的元素类型、iterable 的内容、callable 的参数或返回签名,以及 mixed 内部任何类型。PHPStan 的作者、也是第一个在 PHP 中引入 docblock 泛型的人 Ondřej Mirtes,在同一个邮件线程中指出,在已发布的 PHP 版本中,已经存在一些角落——写了原生类型语法却默默地永远不会被强制检查。所以"原生语法绝对不能撒谎"这条规则,PHP 很久以前就已经打破了,而大家依然愉快地继续写 PHP。Attribute 就是最干净的例子:一个原本只存在于 docblock 中的概念,获得了真正的语法但除了反射之外没有运行时效果,而社区非常喜爱它们。
"用户会感到困惑"这个担忧其实根本站不住脚,因为十年时间足以让这个担忧成为现实,但它并没有。Azjezz 的回应是值得被铭记的一段话:
其核心前提是可以用经验测试的,而这个测试已经运行过了:PHP 在 docblock 中使用泛型已经十年,并被所有主流框架所采用……你所描述的那种失败模式,在当前系统下本应是最容易发生的,但它并没有发生。
而这背后的原因简单到几乎乏味。那些在乎到会写 Collection<User> 的人,正是那些会运行静态分析工具的人。"使用泛型"和"不运行任何检查工具"之间的重叠,在实践中几乎为零。当然,Gina Banyard 质疑乐观的"90% 项目使用静态分析"的说法是有道理的——JetBrains 的调查显示这个数字接近 44%——不打算否认这一点。但这 44% 恰恰就是那些会使用泛型的人群。没有人会在整个代码库中手写 <TKey, TModel> 注解,然后什么工具都不运行。
他们在等待一套行不通的运行时泛型
那么,如果反对意见实际上站不住脚,为什么会有 12 张反对票?因为大多数反对者仍然在等待具象化泛型——在运行时检查的类型——他们宁愿什么都没有,也不愿现在接受擦除式泛型。
在这一点上与大多数人意见不同:不想要运行时检查的泛型。不是作为将来的目标,也不是作为擦除式泛型暂时代替的"真正版本"。擦除式就是实际想要的那个模型,因为它零成本,并且和生态系统已有的工作方式一致。不过,先放下个人偏好不谈,因为即使从反对者自己的立场来看,这种坚持也没有道理。PHP 尝试构建运行时泛型已经十年了,却总在同一条路上撞上同一堵墙。Nikita Popov 早在 2020 年就做过原型,并详细阐述了为什么单态化在动态引擎里"行不通"。PHP 基金会在 2024 年重新捡起这项工作,结果当泛型遇到联合类型时,立即遇到了超线性类型检查的问题。到 2025 年,官方路线已经全面退缩到"仅编译时"泛型,new Repository<BlogPost>()、联合类型和类型推导全部被明确标记为"真的真的很难™、真的真的很慢™,或者两者兼有"。而当 Rob Landers 在大约一周内将具象化实现挂载到这个分支上时,测试数据表明泛型密集的代码慢了 30% 到 50%,最坏情况下接近 2 倍。这种开销会通过依赖图向下游传导——任何仅仅依赖某个使用了泛型的库的应用,都要承担这个代价。
这就是灌木丛中的那只鸟。而反对票正在放手手中的那只,只为继续盯着它。他们真正要求的标——在考虑擦除式之前必须先证明具象化泛型不可能——是一个永远无法满足的标准。对于一台没有人有时间、资金或授权去重写的引擎,无法证明一个否定命题。
真正会使用它的人没有投票权
到了某个时刻,这不再像是一个技术裁决,而更像是一个流程失败。讨论由静态分析领域的人主导,他们绝大多数表示支持。投票者则主要是运行时引擎开发者。正如 Psalm 的作者 Matthew Brown 所说,"这两类人之间的重叠并不多"。那些每天都会使用这个特性的人没有投票权;那些永远不会写 Collection<User> 的人却有。
Brown 不仅在 Reddit 上说过。在 internals 邮件列表的讨论中,他告诉开发者们"2004 年做出的决定不应该主导今天的决策":运行时检查在当年没有更好方案时有意义,但如今静态分析能比运行时发现更多错误,更早、更快。这个人编写了整个生态系统所依赖的两个工具之一,而他的名字却不在投票名单里。一个被开发者们期盼了十年的特性正在消亡,因为会用的人不是做决定的人。就连劝说 Azjezz 推迟投票的 Larry Garfield 也是因为这个原因——用他的话说,"核心团队否决泛型,对每一个关心 PHP 的人来说,都是最糟糕的结果"。他说得对。而这件事很可能还是会照常发生。
也应当给反对派应有的评价,因为并不认为这个 RFC 是完美无缺的。它并不是一个完全干净的擦除模型。它确实存在编译时和链接时的执行缺口,而且担心它的发布可能会给未来的具象化设计带来更棘手的破坏性变化——这个担忧是合理的,不是 FUD。但投赞成票的 Nicolas Grekas 指出了真正重要的事情:docblock 泛型之所以可被采纳,正是因为它们对引擎不可见;而原生的 <T> 可以遵循同样的渐进路径——PHP 在返回类型、可空参数以及其他所有特性上已经走过了这条路。
所以,并不对投票失败感到惊讶。只是对这种局面感到厌倦。PHP 本可以拥有泛型。它每天都在运行泛型,依托于每一个我们所依赖的框架。而它恰恰正在拒绝这些泛型,只为等待一个它花了十年证明自己无法构建的版本。这才是悲剧。不是灌木丛中的鸟难以捕捉,而是我们一再放手手中的那只,只为站在那里盯着它看。说实话,这真的很可悲。
来自 RFC 作者 Azjezz
作为 RFC 的作者,询问 Azjezz 是否愿意发表意见。他表示没有时间撰写一篇措辞优美的博文,但确实想参与,并允许从他在 Reddit 上的一篇近期评论中引用内容。在那篇评论中,他解释了为什么在投票很可能失败的情况下仍然开启了投票。Azjezz 解释说,讨论的焦点已经从 RFC 本身(关于运行时忽略泛型)转向了另一种诉求——人们想要具象化(运行时检查)泛型。他解释道:
第二类情况值得坦诚面对。原则上不反对具象化泛型。如果它们在今天的 PHP 中可行,也更愿意选择它们。没有将 RFC 转向具象化的原因是,"设计得更好"并不是问题所在。Rob Landers 在大约一周内在这个分支上实现了具象化泛型,测试数据表明泛型密集的代码慢了 30%–50%,最坏情况下接近 2 倍。
这种开销会通过依赖图不断累积。如果 Psl(或 Symfony、PHPUnit、Laravel、Doctrine 等)在具象化模型下发布了原生泛型,每一个下游应用都要为此买单,即使那些从未声明过自己的泛型的应用也无法幸免。把这个成本放到 CI 上,10 分钟的构建会变成 15 到 20 分钟,蔓延到整个生态系统。这样的数据,人们同样不会投赞成票。
关于为什么 Azjezz 在未进一步探索在 RFC 之上添加具象化泛型的可能性就提前开启投票,他这样回答:
老实说:不会把时间花在具象化泛型上,因为在他看来,它们不会发生。不是因为不想要它们——他确实想要。要使它们在 PHP 中达到可以正式发布的性能水平,需要对引擎进行结构性改造,而他既没有时间、自由,也没有这个授权去做。他只是一个利用业余时间、在各项事务间隙中工作的个人。要求他重写 PHP 引擎以使具象化泛型可行,并在这个过程中破坏现有功能,这并不是一个现实的请求。而在没有这种重写的情况下发布具象化 RFC,只会让现状走到 Rob 的分支已经到达的地方:一个可以工作但性能表现令社区无法接受的实现。
如果具象化泛型有一天在 PHP 中变得可行,那一定是因为某个有时间、有资源、有引擎层面授权的人开辟了那条道路。一旦这种情况发生,也会很乐意支持后续的 RFC。在那之前,"静态分析收敛性是否值得发布边界擦除式泛型"才是选票上真正的问题。
来自 Laravel 高级软件工程师 Nuno Maduro
自 PHPStan 问世以来,Nuno 几乎在其所有代码中通过 PHPStan 使用泛型。到了今天,这感觉像是任何现代语言的必备特性。如果 PHP 想继续朝着这个方向前进,它需要泛型。
来自 JetBrains PHP 开发者倡导者 Brent Roose
信不信由你,但 Brent 对最近的 RFC 正在失败这件事并没有那么在意。他也希望它能通过,但泛型在大局上并不会带来本质区别。那个"大局"要重要得多,如果说有什么意义的话,泛型 RFC 反而凸显了 PHP 在这个方面的失败。
无论人们是否愿意接受,PHP 不仅仅是解释器,也不仅仅是语法。PHP 之所以有今天的地位,不是因为语言本身有多优美,而是因为它拥有丰富的生态系统。PHP 不仅仅是一门编程语言,如果没有框架、包和工具构成的生态系统,它很可能早已不复存在。
与此同时,大约 100 人组成的一个群体正在决定这门语言的未来(严格来说,大约有 2000 人有资格投票,但大多数人已经不再参与了——这本身就很令人震惊)。没有领导者或实体来设定愿景,而这个群体内部严重分裂——例如,他们会花费数周争论是否应该从官网上移除一个 X 链接。
有人说,缺乏统一的愿景和方向正是 PHP 的伟大之处,但 Brent 认为这正在严重拖累 PHP。那些还没有使用 PHP 的公司,为什么会选择一门设计权不属于任何人的语言?在这个体系中,唯一的受薪实体随时可能因为一小群人的反对而被阻止前进?而这个群体几乎没有任何来自真正驱动 PHP 发展的最大生态系统——如 Laravel、Symfony、WordPress 或 Packagist——的代表。
在 Brent 看来,这就是泛型 RFC 以及在此之前许多 RFC 所揭示的失败。过去曾有人试图改变这个体系,但徒劳无功。委员会似乎对现状感到满意,并不希望流程发生改变。
不过,Brent 仍然抱有希望。PHP 在过去也曾经历过同样缺乏方向和愿景的阶段。之后也出现过语言大步向前的时期——比如早期的 Zend 时代;然后是 PHP 7.0 重写时 Hack 语言步步紧逼;再后来是 Nikita 在 7.x 后期和 8.x 初期推动语言向前发展。最近似乎再次失去了方向,但 Brent 也乐观地认为,合适的人或实体最终会出现。
如果这意味着暂时无法拥有泛型,那也没关系。优秀的开发者们会继续使用 PHP,在没有泛型的情况下构建出出色的产品。

浙公网安备 33010602011771号