PHP 与继承类的协方差 - 声明不兼容

标签 php covariance

我想创建一个带有抽象方法的抽象类,它允许在返回类型中使用抽象类型。在我的最后一个类中,我想覆盖返回的类型,该类型实现了最初声明的抽象类型。

<?php

abstract class A {
    abstract public function test(A $foo): self;
}

class B extends A {
    public function test(B $foo): self
    {
        return $this;
    }
}

抛出这个编译错误:

Fatal error: Declaration of B::test(B $foo): B must be compatible with A::test(A $foo): A in ... on line 8

In documentation, covariance is explained with interface.但不是抽象类。有关 PHP 实现的更多信息,文档说:

In PHP 7.2.0, partial contravariance was introduced by removing type restrictions on parameters in a child method. As of PHP 7.4.0, full covariance and contravariance support was added.

我使用的是 PHP 7.4。

最佳答案

面向对象编程的一个非常核心的原则是 Liskov substitution principle,它本质上可以归结为:

if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program

实现这一点的方法是让协变方法返回类型,逆变方法类型参数。抛出的异常在这里算作返回类型,因此它们也需要是协变的。

您需要的是打破此原则的类型参数的协方差。 原因可以通过考虑下面的例子看出:

abstract class A {
    abstract public function test(A $foo): self;
}

class C extends A {
    public function test(C $foo): self {
        return $this;
    }
}

class B extends A {
    public function test(B $foo): self {
        return $this;
    }
}

$b = new B();
$c = new C();

$b->test($c); // Does not work
((A)$b)->test((A)$c); // Works


在上面的示例中,您不允许 B::test 接受除 B 之外的任何类型作为类型参数。然而,由于 B 本身是 A 的 child ,而 C 通过简单的向下转换也是 A 的 child (这是允许的)限制被绕过。您始终可以禁用向下转换,但这几乎等于是在禁用继承,这是 OOP 的核心原则。

现在当然有令人信服的理由允许类型参数的协变,这就是为什么某些语言(例如 Eiffel )允许它,但是这被认为是一个问题,甚至被命名为 CATcalling(CAT 代表更改可用性或类型)。

在 PHP 中,您可以尝试进行运行时检查以纠正这种情况:

abstract class A {
    abstract public function test(A $foo) {
         // static keyword resolve to the current object type at runtime 
         if (!$foo instanceof static) { throw new Exception(); }  
    }
}

class C extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

class B extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

然而,这有点困惑而且可能是不必要的。

关于PHP 与继承类的协方差 - 声明不兼容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61403800/

相关文章:

php - 什么是 Mysql 链接?

scala - 具有多重约束的协方差类型参数

inheritance - 如果 Car 是 Vehicle 的子类型,为什么 Vehicle->void 被视为 Car->void 的子类型?

php - 如果它们具有相同的名称,则回显 fetch_array 行

php - 在 zend 框架中包含 js 文件的最佳方式

年度 PHP 表单验证

php - jQuery、javascript 数组/对象 SQL 注入(inject)问题

.net - U[] 是如何转换为 T[] 的?

Kotlin中抽象容器工具的泛型输入/输出?

c# - 为什么 Task<T> 不是协变的?