具有抽象 class 和 PHP 中的特征的类型协方差

Type covariance with abstract class and traits in PHP

我正在开发一个 PHP (7.4) 库,需要为新功能使用特征,但我遇到了参数类型协方差的问题。

我有一个像这样的摘要 parent class :

<?php

abstract class ParentClass {
    abstract public function parentMethod($param): bool;
}

?>

我也有一个特点:

<?php

trait MyTrait {
    abstract public function traitMethod($param): bool;
}

?>

我在 child class 中同时使用了 class 和 trait :

<?php

class ChildClass extends ParentClass {

    use MyTrait;

    // implementation of the abstract methods

    public function parentMethod(int $param): bool { // change parent method parameter type
        // implementation
    }

    public function traitMethod(int $param): bool { // change trait method parameter type
        // implementation
    }
}

?>

这里的问题是我得到了这个错误:

Fatal error: Declaration of ChildClass::parentMethod(int $param): bool must be compatible with ParentClass::parentMethod($param): bool

看来我无法更改 parentMethod() 参数类型。如果我删除 parentMethod() 定义中的 int 类型,我不会收到错误!即使在特征方法上有一个特定的类型参数。

为什么我可以在 trait 抽象方法中使用协变参数类型,但不能在抽象 class 方法中使用?

Covariance 和 Contravariance 是与继承相关的概念,使用 trait 不是继承。

注:以上说法不完全正确,我会在本回答最后说明原因。

来自PHP documentation

A Trait is similar to a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance.

为什么会看到这个错误?

因为 int 不是 everything 的超类型,也不是表示任何类型的伪类型(将 int 替换为 mixed在 PHP 8 中,看看会发生什么)。另外 Type widening 不允许使用任意超类型(你只能省略类型)

例如,假设您这样定义父方法:

abstract public function parentMethod(int $param): bool;

类型扩展允许您在 ChildClass.

中仅省略 $param 数据类型

Contravariance, allows a parameter type to be less specific in a child method, than that of its parent

所以假设我们有另一个名为 C 的 class 扩展 stdClass 并且我们将 parentMethod 定义为仅接受 C

类型的对象
class C extends stdClass {}

abstract class ParentClass
{
    abstract public function parentMethod(C $param): bool;
}

现在 ChildClass 如果我们实现 parentMethod 以接受类型 stdClass

的对象
public function parentMethod(stdClass $param): bool
{ 
    
}

这将起作用并且不会发出任何错误。

逆变.

#编辑 至于你的问题在评论

为什么可以在子class中键入实现的特征方法的参数?

因为特征是 复制粘贴 到 class 中,您不能对它们强加 OOP 规则。这就是为什么你可以覆盖特征中的 final 方法。

trait Foo
{
    final public function method($var)
    {
        return $var;
    }
}
class Bar
{
    use Foo;
    // "Override" with no error
    final public function method($var)
    {
        return $var;
    }
}

trait 中抽象方法的重点是强制显示 class 实现它们(类型和访问修饰符可能不同 )

The PHP documentation

Caution A concrete class fulfills this requirement by defining a concrete method with the same name; its signature may be different.

2020 年 10 月更新

从 PHP8 开始,特征中抽象方法的行为已经改变,现在具有不匹配签名的抽象方法将失败并出现致命错误,LSP 规则将应用于它们。

好的,这是为什么?

整个变化始于 bug report, and there has been some discussion here

似乎在 PHP 8 之前存在某种行为冲突:

而且因为 abstract 本身表示一个合同,所以你所质疑的是合法的,现在 PHP 8 你会很高兴 https://3v4l.org/7sid7 :).