PHP协变与逆变困惑:子类属性类型提示报错咨询
为什么子类属性不能使用协变类型(即使实现了父类接口)
核心原因:PHP属性类型是严格不变的
PHP中,类的属性类型遵循不变性(invariant)规则——子类不能修改父类属性的类型,哪怕子类的类型是父类类型的实现/子类。而你熟悉的协变、逆变规则,仅适用于方法的返回值(协变)和方法参数(逆变),不适用于属性。
背后的逻辑:维护类型安全与里氏替换原则
假设PHP允许你把D::$prop的类型从A改为B,会直接破坏类型安全,违反里氏替换原则(LSP):
任何接受父类C的代码,都应该能无缝替换为子类D。比如这段代码:
function updateCProp(C $c) { // 按照父类C的契约,$prop可以是任何实现A的类,比如新增的匿名类 $c->prop = new class implements A { public function foo() {} }; } $d = new D(new B()); updateCProp($d); // 这里会直接报错:无法将A的实现类赋值给B类型的属性
如果PHP允许子类修改属性类型,这段从父类角度完全合法的代码,会在运行时触发类型错误。为了避免这种潜在的安全问题,PHP在编译阶段就直接禁止了属性类型的变更。
你的代码的修正方案
不需要重新声明属性类型,而是在子类构造函数中约束参数类型,再通过类型断言告诉PHP属性的实际类型:
class D extends C { public function __construct(B $prop) { parent::__construct($prop); // 直接对构造参数调用B的方法(参数已约束为B类型) $prop->bar(); // 若要通过$this->prop调用,需加类型断言明确实际类型 /** @var B $propInClass */ $propInClass = $this->prop; $propInClass->bar(); // 或者用断言做运行时检查 assert($this->prop instanceof B); $this->prop->bar(); } }
这样既满足了子类对B类型的需求,又不会破坏父类的类型契约。
内容的提问来源于stack exchange,提问作者Vincent PHILIPPE
相关产品推荐
相关产品推荐

