PHP8子类重写父类方法参数不一致报错的解决办法

概述

升级到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();

发表评论

邮箱地址不会被公开。 必填项已用*标注