本文介绍如何在 php 缺乏原生泛型支持的前提下,通过 psalm 等静态分析工具的模板注解(`@template`)模拟类型专用容器,兼顾代码复用性(dry)与方法签名的类型准确性,避免 lsp 违反风险。
在 PHP 开发中,我们常需构建多种语义明确、类型受限的“容器”类(如 CookieBag、CandyBag),以提升领域建模清晰度与 IDE 支持体验。但若采用继承方式强行覆盖 get()/set() 的参数与返回类型(如将 mixed 替换为 ?Cookie),虽看似直观,实则违反里氏替换原则(LSP):子类无法完全替代父类使用场景,导致依赖 BagInterface 的通用逻辑(如 GrandMa::giveCookie())在传入 CookieBag 时因类型契约不兼容而失效。
PHP 目前不支持原生泛型(如 GenericBag
以下是一个可落地的实践方案:
*/
private array $bag = [];
public function has(string $key): bool
{
return array_key_exists($key, $this->bag);
}
/**
* @param string $key
* @param T|null $fallback
* @return T
*/
public function get(string $key, $fallback = null)
{
return $this->has($key) ? $this->bag[$key] : $fallback;
}
/**
* @param string $key
* @param T $value
* @return static
*/
public function set(string $key, $value): self
{
$this->bag[$key] = $value;
return $this;
}
/**
* @param string $key
* @return void
*/
public function del(string $key): void
{
unset($this->bag[$key]);
}
/**
* @return array
*/
public function all(): array
{
return $this->bag;
}
/**
* @param callable(mixed, string): bool $callback
* @return array
*/
public function filter(callable $callback): array
{
return array_filter($this->bag, $callback, ARRAY_FILTER_USE_BOTH);
}
} ✅ 关键要点说明:

使用时,无需创建子类,直接实例化并借助类型注解声明语义:
*/ $cookieBag = new GenericBag(); /** @var GenericBag*/ $candyBag = new GenericBag(); // ✅ 正确:类型匹配 $cookieBag->set('session', new Cookie()); $cookie = $cookieBag->get('session'); // ❌ Psalm/PHPStan 将报错:Expected Cookie, got Candy // $cookieBag->set('bad', new Candy()); class GrandMa { /** * @param GenericBag $bag */ public function giveCookie(GenericBag $bag): void { $bag->set('gift', new Cookie()); // ✅ 类型安全调用 } }
⚠️ 注意事项:
总结而言,在 PHP 当前生态下,@template + 工具链支持是最务实的泛型模拟方案:它让抽象容器真正“一次编写、多处强类型复用”,在不增加运行时开销的前提下,显著提升大型项目的可维护性与协作效率。