如何遵守协变/逆变规则同时避免编写重复的PHP业务代码?
解决方案
核心思路是通过泛型约束+运行时校验在不改动原有架构的前提下满足所有要求,所有语法都是PHP原生支持的规范写法,无hack逻辑。
改动点
- 把
BaseBarClass改造为泛型基类,约束关联的Foo子类类型 - 新增抽象方法要求每个Bar子类返回对应Foo类的完全限定名,替代原有字符串拼接类名的不安全写法
- 在基类
getFooBase中增加运行时类型校验,保证返回结果类型准确 - 子类
getFoo方法仅需做参数处理和类型声明,无需重复公共逻辑
改造后完整代码
class BaseFooClass { protected $keys = []; private $map = []; public function __construct($keyValuePairs) { foreach($this->keys as $key => $value) { $this->map[$key] = $keyValuePairs[$key] ?? null; } } } class ChildFooClass1 extends BaseFooClass { protected $keys = ['foo1_a', 'foo1_b']; } class ChildFooClass2 extends BaseFooClass { protected $keys = ['foo2_a', 'foo2_b', 'foo2_c']; } /** * 泛型声明:约束当前Bar类关联的Foo类必须是BaseFooClass的子类 * @template T of BaseFooClass */ abstract class BaseBarClass { /** * 子类实现,返回对应Foo类的完全限定名 * @return class-string<T> */ abstract protected function getFooClassName(): string; /** * 子类实现,返回对应的classIndex */ abstract protected function getClassIndex(): int; /** * 公共逻辑全部保留在基类,子类无需重复实现 * @return ?T */ protected function getFooBase(int $dataIndex) : ?BaseFooClass { $keyValuePairs = GetRemoteData($this->getClassIndex(), $dataIndex); if (checkDataIntegrity($keyValuePairs)) { $className = $this->getFooClassName(); $instance = new $className($keyValuePairs); // 运行时类型校验,保证返回结果完全符合类型要求 if ($instance instanceof $className) { return $instance; } } return null; } } class ChildBarClass1 extends BaseBarClass { protected function getFooClassName(): string { return ChildFooClass1::class; } protected function getClassIndex(): int { return 1; } public function getFoo(int $dataIndex) : ?ChildFooClass1 { $result = $this->getFooBase($dataIndex); // 可选:如果不需要兼容老版本静态分析工具,下面的判断可以省略,加一行@var注解即可 if ($result instanceof ChildFooClass1 || is_null($result)) { return $result; } throw new \RuntimeException('类型不匹配'); } } class ChildBarClass2 extends BaseBarClass { protected function getFooClassName(): string { return ChildFooClass2::class; } protected function getClassIndex(): int { return 2; } public function getFoo($someInput) : ?ChildFooClass2 { $dataIndex = $this->calculateDataIndex($someInput); $result = $this->getFooBase($dataIndex); if ($result instanceof ChildFooClass2 || is_null($result)) { return $result; } throw new \RuntimeException('类型不匹配'); } private function calculateDataIndex($someInput): int { // 原有计算逻辑 return 0; } }
符合要求的说明
- 严格限制返回类型:
- 静态层面:泛型约束+
class-string类型声明,所有主流静态分析工具(PHPStan、Psalm、PHPStorm)都可以准确识别返回类型,不会出现类型不匹配提示 - 运行时层面:基类和子类都有类型校验,不可能返回不符合声明的实例
- 静态层面:泛型约束+
- 避免重复代码:所有公共逻辑(远程拉取、数据校验、实例化)全部保留在基类
getFooBase中,新增逻辑只需要修改基类即可,子类不需要重复实现 - 严格遵守协变/逆变规则:基类返回
BaseFooClass,子类返回对应的Foo子类,属于合法的返回类型协变,完全符合PHP的类型系统规则,没有任何语法违规
内容的提问来源于stack exchange,提问作者cr001
相关产品推荐
相关产品推荐

