【问题标题】:PHP 8 Method overriding with different types from the same BaseClassPHP 8方法覆盖来自同一BaseClass的不同类型
【发布时间】:2020-12-29 15:21:39
【问题描述】:

我的项目在 PHP 7.X 上运行良好,升级到 PHP 8 后出现以下问题,我不知道如何解决。

我有以下(简化的)情况:

<?php

class Vehicle
{
    //...
}

class Car extends Vehicle
{
    //...
}


class VehicleOutputMaker
{
    public function output(Vehicle $entity)
    {
        
    }
}


class CarOutputMaker extends VehicleOutputMaker
{
    //THROWS EXCEPTION
    public function output(Car $entity)
    {
        parent::output($entity);
    }
}

我的整个项目都是这样运行的,但是从 PHP 8 开始,我得到了一条异常

public function output(Car $entity)

带有“致命错误:*** 的声明必须与 *** 兼容”

它与 PHP 7 完美配合!因为“汽车”也是车辆。

有人知道如何解决这个问题吗?

谢谢!

【问题讨论】:

  • 它不能与 PHP 7 完美配合,唯一的区别是你得到了 Warning 而不是 Fatal error。阅读继承的工作原理inheritance
  • 是的,谢谢,这确实是 PHP 7 中的一个警告,现在它是一个致命错误。但我不明白他们为什么现在这么严格。 CarVehicle 和当我将类型提示从 Car 更改为 Vehicle 时,它可以工作,是的。但是我仍然可以将Car 对象传递给该方法,尽管类型是Vehicle 并且设置了declare(strict_types = 1)。我希望将Car 对象传递给需要Vehicle 的方法时会出现致命错误,但是......它可以工作。而且我上面的声明说的是一样的,但不会被 PHP 8 接受...
  • “但我仍然可以将 Car 对象传递给方法,尽管类型是 Vehicle”......是的,很明显,因为 Car 是 Vehicle 的一种类型。这就是继承的全部意义。
  • 但基本上问题是使用与父类中不同的签名覆盖方法是不合逻辑的。在其他强类型 OO 语言中你也无法做到这一点。

标签: php php-8


【解决方案1】:

您收到警告而现在出现致命错误的原因是您的代码不遵守子类型中方法参数类型的contravarianceLiskov substitution principle (LSP) 标准要求。

考虑到这一点,问题在于,虽然子类中的方法可以扩展参数类型的范围,但它必须接受父类接受的所有参数类型。

您的子类CarOutputMaker 违反了此规则,它的方法output() 接受Car 类型的参数(Vehicle 的子类型),但不接受超类型Vehicle 的参数为在其父方法VehicleOutputMaker::output()中声明。

所以这在 PHP7 中是有效的:

class VehicleOutputMaker  
{  
    public function output(Vehicle $entity)  
    {  
    }  
}  
  
class CarOutputMaker extends VehicleOutputMaker  
{  
    public function output(Vehicle $entity)  
    {  
        parent::output($entity);  
    }  
}

值得注意的是,由于您使用的是 PHP8,因此您可以使用 union types 在您的子类中输入两种类型:Vehicle|Car

class VehicleOutputMaker  
{  
    public function output(Vehicle $entity)  
    {  
    }  
}  
  
class CarOutputMaker extends VehicleOutputMaker  
{  
    public function output(Vehicle|Car $entity)  
    {  
        parent::output($entity);  
    }  
}

【讨论】:

    【解决方案2】:

    为什么不把Car的类型提示改成Vehicle,这样就尊重父类的方法签名了。

    如果您想要执行特定于 Car 的行为,则必须在 CarOutputMaker 的输出方法中键入检查并将车辆类型转换为 Car。

    class CarOutputMaker extends VehicleOutputMaker
    {
        //THROWS EXCEPTION
        public function output(Vehicle $entity)
        {
            parent::output($entity);
        }
    }
    

    【讨论】:

    • 是的,这是可能的,但我知道此方法将始终使用 Car 而不是 Vehicle 调用。但是当我想支持 PHP 8 时,我需要重写它。似乎没有办法只声明一个标志并继续工作..
    猜你喜欢
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    • 1970-01-01
    • 2018-08-31
    相关资源
    最近更新 更多