引言:为什么PHP开发者渴望泛型
如果您曾经写过Java、C#或TypeScript,然后又回到PHP,您很可能感到一丝失望。在这些语言中,有一个优雅的泛型(Generics)系统,它允许创建灵活且类型安全的数据结构。而在PHP中,长期以来一直存在着“类型化无政府状态”:数组可以包含任何内容,IDE只能高亮显示最明显的错误。
但时代在变化。从PHP 7.0开始,尤其是在PHP 8.0/8.1/8.2时代,社区找到了在PHP中使用泛型的方法。是的,在语言语法层面,它们仍然不存在(并且在未来几年内很可能也不会出现),但这并不意味着您今天不能享受泛型编程的所有好处。
在本文中,我们将探讨PHP中带有泛型的类型化是如何工作的,有哪些工具可用于此(PHPStan、Psalm),以及如何编写既灵活又安全的代码。系好安全带——精彩内容即将开始!
什么是泛型,为什么PHP需要它们?
泛型是一种允许用类型参数化类、接口和函数的机制。您不必为每种数据类型编写单独的实现,而是创建一个通用实现,并在使用时指定具体类型。
想象一下,您正在编写一个队列(Queue)类。没有泛型,您只能在其中存储一种数据类型,或者使用mixed(这会破坏所有类型化)。使用泛型,您可以这样说:“这是一个整数队列”或“这是一个User对象队列”。
“原始”类型化的问题
看看这段典型的PHP代码:
<?php
class Collection { private array $items = [];
public function add($item): void { $this->items[] = $item; }
public function get(int $index) { return $this->items[$index] ?? null; }}
$users = new Collection();$users->add(new User('Alice'));$users->add(new User('Bob'));$users->add('这是一个字符串,不是用户'); // 错误!但PHP会保持沉默
$user = $users->get(2); // 返回一个字符串,IDE不会察觉$user->getName(); // 运行时致命错误这是PHP开发者的经典问题:在编写代码时缺乏类型控制。您只有在运行时才会发现错误,而此时用户已经点击了按钮。泛型从根本上解决了这个问题。
PHP中的泛型:实际如何工作
由于PHP中没有原生的泛型语法,我们使用PHPDoc中的注解,这些注解由静态分析器——PHPStan和Psalm——处理。这些工具检查代码是否符合声明的类型,实际上模拟了泛型的工作。
基本语法:模板类型
假设我们想让我们的集合类型安全。以下是通过PHPDoc使用泛型的样子:
<?php
/** * @template T */class GenericCollection { /** @var array<int, T> */ private array $items = [];
/** * @param T $item */ public function add($item): void { $this->items[] = $item; }
/** * @param int $index * @return T|null */ public function get(int $index) { return $this->items[$index] ?? null; }}
// 现在使用:/** @var GenericCollection<User> $users */$users = new GenericCollection();$users->add(new User('Alice'));$users->add('字符串'); // PHPStan会报错:期望类型User,得到string
$user = $users->get(0); // PHPStan知道这是Userecho $user->getName(); //