PHP 7.4 是协变与逆变支持的分水岭,自该版本起才真正支持参数逆变(子类方法可接受更宽泛的父类类型)和返回类型协变(子类方法可返回更具体的子类类型),此前版本严格要求类型完全一致。

PHP 7.4 是协变/逆变支持的分水岭
PHP 7.4 之前,接口方法的参数和返回类型必须严格一致,子类实现哪怕只改一个类型提示(比如把 string 换成 int|string),就会报 Fatal error: Declaration of ... must be compatible with ...。7.4 开始才真正支持参数逆变与返回类型协变,但仅限于继承链中存在明确父子关系的类或接口类型——不是所有联合类型都能乱用。
常见错误现象:
- 在 PHP 7.3 环境下跑带
array|Config参数的子类方法,直接 fatal - 用
stdClass替代Config声明参数,以为是“更宽”,结果运行时报错:类型不兼容,因为stdClass并非Config的父类
参数逆变只允许“向上拓宽”,不能“横向替换”
逆变的本质是:子类方法能接收比父类更宽泛的输入,前提是这个“宽泛”必须能安全替代原类型。例如父接口要求 Config,子类可声明接受 Config|array,但前提是 array 在逻辑上能被当作 Config 安全使用(比如内部做了类型适配);否则就只是类型欺骗,PHP 8+ 的静态分析器(如 PHPStan)会标红。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 逆变参数应优先使用真实父类,如
AnimalShelter::adopt(Animal $animal)→DogShelter::adopt(Mammal $animal)(Mammal是Animal的父类) - 避免用
array或stdClass冒充具体类,除非你同时在方法体内做显式类型校验和转换 - PHP 8.0+ 支持默认参数,但逆变不改变默认值绑定逻辑:父类有
function foo(?string $x = null),子类不能改成function foo(string|null $x = 'default'),因为默认值变更不属逆变范畴
协变返回类型在 PHP 8.3+ 变得更严格
PHP 8.3 起,static 返回类型强制协变:如果父类方法返回 static,子类重写时不能改成具体类名(如 UserBuilder),否则 IDE 补全失效、LSP 违反,且 PHP 解析器直接报 Declaration must be compatible。
关键差异:
-
self不再自动协变,它始终绑定声明时的类;static才随调用者动态变化 - 返回
Dog替代Animal合法(协变),但返回string或array直接报错(非子类型) - 接口中声明
function create(): Animal,实现类返回Dog没问题;但如果实现类又继承自另一个抽象类,该抽象类里同名方法返回Cat,那就会冲突——PHP 不允许多重协变路径
接口方法签名兼容性检查容易忽略的点
很多人只关注返回类型,却忘了参数顺序、可变参数、引用传递这些细节也参与签名比对。PHP 对接口契约的校验是整体性的。
容易踩的坑:
- 父接口方法是
function handle(Config $c, string $id),子类实现写成function handle(string $id, Config $c)→ 参数顺序错,不兼容 - 父类用
&$config引用传参,子类去掉&→ 签名不一致,报错 - PHP 8.4+ 支持命名参数,但接口方法定义里不能写参数名,子类实现也不能靠命名来绕过位置匹配
- 接口方法带默认参数(PHP 8.0+ 允许),子类实现时若省略该参数,默认值不会自动继承,必须显式写出或保持一致
最常被忽略的是:协变/逆变只作用于类型声明本身,不延伸到类型别名、泛型模拟(如 PHPDoc 中的 @template)或运行时类型判断逻辑。那些地方仍需手动保障类型安全。



















