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



我正在开发一个 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::p arentMethod(int $param(:bool

必须与 ParentClass::p arentMethod($param(: bool

兼容

似乎我无法更改父方法((参数类型。如果我删除 parentMethod(( 定义上的int类型,我不会收到错误!即使在特质方法上具有特定的类型参数。

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

协方差和逆变是与继承有关的概念,使用特征不是继承。

注意:上述陈述并不完全正确,我将在此答案末尾解释原因。

来自 PHP 文档

特征类似于类,但仅用于以细粒度和一致的方式对功能进行分组。无法单独实例化特征。它是对传统继承的补充,可以实现行为的水平组合;也就是说,类成员的应用不需要继承。

为什么会看到此错误?

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

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

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

类型加宽允许您仅省略$paramChildClass中的数据类型。

逆变,允许参数类型在 子方法,而不是其父方法的方法

因此,假设我们有另一个名为C的类,它扩展了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

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

好吧,这是为什么?

整个更改始于错误报告,这里已经进行了一些讨论

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

  • 这将适用于 https://3v4l.org/2pERT

  • 这不会 https://3v4l.org/ouO0R

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

最新更新