具有抽象 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 不是继承。
注:以上说法不完全正确,我会在本回答最后说明原因。
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 实现它们(类型和访问修饰符可能不同 )
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 :).
我正在开发一个 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 不是继承。
注:以上说法不完全正确,我会在本回答最后说明原因。
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 实现它们(类型和访问修饰符可能不同 )
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 :).