PHPStan父子类继承属性类型报错 兼容PHP5.6方案咨询
PHPStan父子类类型校验报错修复方案
固定报错类型
静态检测时出现两类可复现报错,代码实际运行无异常:
Parameter #1 $a of method xxx expects ConcreteChildClass, AbstractParentClass given:方法xxx的第1个参数$a声明要求传入ConcreteChildClass类型,静态分析判定传入值为AbstractParentClass类型Property ClassName::$a (ConcreteChildClass) does not accept AbstractParentClass:类ClassName的$a属性声明为ConcreteChildClass类型,静态分析判定赋值内容为AbstractParentClass类型
约束条件
- 代码必须兼容PHP 5.6版本
- 对项目内抽象类的修改权限有限
- 此前尝试
@template注解调整类型声明未达到预期效果
现有核心代码
<?php abstract class AbstractClassA { /** * @return static */ abstract public function postProcess(); } class ConcreteClassA extends AbstractClassA { /** * @return ConcreteClassA */ public function postProcess() { return $this; } } abstract class AbstractClassB { /** * @return AbstractClassA */ abstract public function deserialize(); /** * @return AbstractClassA */ public function someMethodA($params) { return $this->someMethodB(); } /** * @return AbstractClassA */ public function someMethodB() { $object = $this->deserialize(); return $object->postProcess(); } } class ConcreteClassB extends AbstractClassB { /** * @return ConcreteClassA */ public function deserialize() { $object = new ConcreteClassA(); // some assignements return $object; } }
调用场景代码
class SomeClass { /** @var ConcreteClassA $propertyA */ public $propertyA; /** @var ConcreteClassB $propertyB */ public $propertyB; public function someMethod() { $this->propertyA = $this->propertyB->someMethodA($params); $this->someProperty->someOtherMethod($this->propertyA); // 这里触发第二个报错:someOtherMethod要求ConcreteClassA,被判定传入AbstractClassA } }
报错根因
PHPStan静态分析时严格遵循父类声明的类型规则:AbstractClassB的someMethodA、someMethodB方法PHPDoc声明返回值为AbstractClassA,即使子类ConcreteClassB重写deserialize返回ConcreteClassA,静态分析也不会自动推导链式调用postProcess()后的返回值为子类类型,始终按照父类声明判定返回抽象父类类型,因此在赋值给子类类型属性、传入要求子类类型的参数时触发类型不匹配报错。
可行解决方案
方案1:调用点类型窄化(无侵入,无需修改抽象类,兼容PHP5.6)
这是权限受限时的首选方案,不需要调整原有抽象类和子类的实现,仅在调用位置处理类型判定即可。
写法1:加PHPDoc类型注解
直接通过注解告诉PHPStan返回值的实际类型,无运行时开销:
public function someMethod() { $result = $this->propertyB->someMethodA($params); /** @var ConcreteClassA $result */ $this->propertyA = $result; $this->someProperty->someOtherMethod($this->propertyA); }
写法2:加instanceof运行时校验
通过类型判断自动触发PHPStan类型窄化,同时增加运行时安全校验,避免后续代码调整返回非预期类型引发逻辑错误:
public function someMethod() { $result = $this->propertyB->someMethodA($params); if (!$result instanceof ConcreteClassA) { throw new \RuntimeException('Invalid return type from someMethodA'); } $this->propertyA = $result; $this->someProperty->someOtherMethod($this->propertyA); }
方案2:子类重写方法补全返回注解(可修改ConcreteClassB时使用)
如果拥有具体子类的修改权限,不需要改动抽象父类,仅需在ConcreteClassB中重写对应方法,补充精确返回值的PHPDoc注解,方法内部直接调用父类实现即可,不会改变运行时逻辑,一次修改后所有调用点都能自动得到正确类型推导:
class ConcreteClassB extends AbstractClassB { /** * @return ConcreteClassA */ public function deserialize() { $object = new ConcreteClassA(); // some assignements return $object; } /** * @return ConcreteClassA */ public function someMethodA($params) { return parent::someMethodA($params); } /** * @return ConcreteClassA */ public function someMethodB() { return parent::someMethodB(); } }
方案3:配置忽略规则(仅临时过渡使用)
如果暂时无法调整代码,可以在PHPStan配置文件phpstan.neon中添加忽略规则屏蔽对应报错,该方案不会解决潜在类型风险,不建议长期使用:
parameters: ignoreErrors: - '#Property .*::\$propertyA \(ConcreteClassA\) does not accept AbstractClassA#' - '#Parameter #1 .* expects ConcreteClassA, AbstractClassA given#'
内容的提问来源于stack exchange,提问作者PLG
相关产品推荐
相关产品推荐

