概述
升级到PHP8后,当子类方法重写父类方法时,如果参数个数与父类不一致,则会报致命错误:
Fatal error: Declaration of xxx must be compatible with xxx in xxx on line xxx
这个错误在PHP7的版本只是一个警告(Warning),可以通过关闭错误提示来忽略,但现在升级到PHP8后,这个错误变为了致命错误(Fatal error),无法忽略了。
原因
出现该问题的原因是违背了里氏替换原则。
可以参考PHP官网:
签名兼容性规则:
https://www.php.net/manual/zh/language.oop5.basic.php#language.oop.lsp
当覆盖(override)方法时,签名必须兼容父类方法。 否则会导致 Fatal 错误(PHP 8.0.0 之前是 E_WARNING 级错误)。 兼容签名是指:遵守协变与逆变规则; 强制参数可以改为可选参数;新参数为可选参数。 这就是著名的里氏替换原则(Liskov Substitution Principle),简称 LSP。 不过构造器和 私有(private)方法不需要遵循签名兼容规则, 哪怕签名不匹配也不会导致 Fatal 错误。
PHP官网中说了,除了构造函数和私有方法不需要遵循签名兼容规则外,都需要遵守该规则。
里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
更多关于里氏替换原则可以查看:http://c.biancheng.net/view/1324.html
解决办法
最佳的解决办法当然是遵守里氏替换原则,修改子类的方法,加上跟父类一样的参数。但如果项目的文件比较多,挨个修改显然工作量很大。可以批量修改父类中的方法,批量将其修改为私有(private)方法,这样就不需要遵守里氏替换原则。
示例1
示例1 将Animal
类中的speak
方法修改为private
,程序可以正常执行。
<?php
class Animal
{
private function speak($name)
{
echo $name . " speak";
}
}
class Dog extends Animal
{
public function speak()
{
echo "Dog barks";
}
}
$dog = new Dog();
$dog->speak();
但有时候子类需要调用父类的方法,如果改成了private
则会提示没有权限访问。这时就需要使用PHP的反射来调用。
示例2
示例2通过反射使子类方法可以调用父类的私有方法。
class Animal
{
private function speak($name)
{
echo $name . " speak";
}
public function __call($name, $arguments)
{
$class = new ReflectionClass(__CLASS__);
$object = $class->newInstance();
$method = new \ReflectionMethod(__CLASS__, $name);
$methodParams = $method->getParameters();
$params = [];
$i=0;
foreach ($methodParams as $methodParam){
$params[$methodParam->getName()] = $arguments[$methodParam->getName()]??$arguments[$i];
$i++;
}
call_user_func_array([$object, $name], $params);
}
}
class Dog extends Animal
{
public function speak()
{
parent::speak('animal');
echo "Dog barks";
}
}
$dog = new Dog();
$dog->speak();