3

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

我有一个像这样的抽象父类:

<?php

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

?>

我还有一个特点:

<?php

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

?>

我在子类中同时使用类和特征:

<?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
    }
}

?>

这里的问题是我收到此错误:

致命错误:声明 ChildClass::parentMethod(int $param): bool 必须与 ParentClass::parentMethod($param): bool 兼容

似乎我无法更改 parentMethod() 参数类型。如果我删除intparentMethod() 定义上的类型,我不会收到错误消息!即使在 trait 方法上有一个特定的类型参数。

为什么我可以将协变参数类型与特征抽象方法一起使用,但不能与抽象类方法一起使用?

4

1 回答 1

4

协变和逆变是与继承相关的概念,使用 trait 不是继承。

注意:上面的说法并不完全正确,我会在这个答案的最后解释为什么。

来自PHP 文档

Trait 类似于类,但仅旨在以细粒度和一致的方式对功能进行分组。无法单独实例化 Trait。它是对传统继承的补充,可以实现行为的横向组合;即类成员的应用不需要继承。

为什么你看到这个错误?

因为int不是所有事物的超类型,也不是代表任何类型的伪类型(在 PHP 8 中替换intmixed,看看会发生什么)。此外,类型扩展不允许使用任意超类型(您只能省略类型)

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

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

类型扩展允许您仅$paramChildClass.

逆变,允许参数类型在子方法中的特定性低于其父方法

因此,假设我们有另一个名为Cextends的类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
{ 
    
}

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

那就是逆变

#Edit 至于你在评论中的问题

为什么可以在子类中键入实现的 trait 方法的参数?

因为特征被复制粘贴到一个类中,所以你不能对它们强加 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;
    }
}

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

PHP 文档指出

注意一个具体的类通过定义一个同名的具体方法来满足这个要求。它的签名可能不同

2020 年 10 月更新

从 PHP 8 开始,特征中抽象方法的行为发生了变化,现在具有不匹配签名的抽象方法将失败并出现致命错误,并且 LSP 规则将应用于它们。

好的,这是为什么呢?

整个变化始于一个错误报告,这里有一些讨论

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

也因为abstract它本身表示合同,因此您所质疑的是合法的,现在使用 PHP 8,您会很高兴https://3v4l.org/7sid7 :)。

于 2020-01-02T21:21:06.193 回答