php 7接口,返回类型提示和self

PHP 7 interfaces, return type hinting and self

我在使用PHP7中的返回类型提示时遇到了一些问题。我的理解是,暗示: self意味着您打算让一个实现类返回自己。因此,我在接口中使用了: self来表示这一点,但当我尝试实际实现接口时,发现了兼容性错误。

以下是我遇到的问题的简单演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface iFoo
{
    public function bar (string $baz) : self;
}

class Foo implements iFoo
{

    public function bar (string $baz) : self
    {
        echo $baz . PHP_EOL;
        return $this;
    }
}

(new Foo ()) -> bar ("Fred")
    -> bar ("Wilma")
    -> bar ("Barney")
    -> bar ("Betty");

预期输出为:

Fred
Wilma
Barney
Betty

我实际上得到的是:

PHP Fatal error: Declaration of Foo::bar(int $baz): Foo must be compatible with iFoo::bar(int $baz): iFoo in test.php on line 7

问题是foo是i foo的一个实现,据我所知,实现应该与给定的接口完全兼容。我可以通过更改接口或实现类(或两者都更改)来修复这个问题,用名称来返回接口提示,而不是使用self,但我的理解是,语义上self意味着"返回刚刚调用方法的类的实例"。因此,将其更改为接口在理论上意味着,当我的意图是针对被调用的实例时,我可以返回实现接口的任何实例。

这是PHP中的一个监督,还是一个深思熟虑的设计决策?如果是前者,是否有机会看到它在php 7.1中被修复?如果不是,那么正确的返回方式是什么,暗示接口期望您返回刚刚调用方法进行链接的实例?


self不引用实例,它引用的是当前类。接口无法指定必须返回同一个实例-以您尝试的方式使用self只会强制返回的实例属于同一类。

也就是说,PHP中的返回类型声明必须是不变的,而您尝试的是协变的。

您使用self相当于:

1
2
3
4
5
6
7
8
9
10
interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : Foo  {...}
}

这是不允许的。

返回类型声明RFC的意思是:

The enforcement of the declared return type during inheritance is invariant; this means that when a sub-type overrides a parent method then the return type of the child must exactly match the parent and may not be omitted. If the parent does not declare a return type then the child is allowed to declare one.

...

This RFC originally proposed covariant return types but was changed to invariant because of a few issues. It is possible to add covariant return types at some point in the future.

目前,你能做的至少是:

1
2
3
4
5
6
7
8
9
10
interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}


它也可以是一种解决方案,即不在接口中显式定义返回类型,只在phpDoc中定义,然后可以在实现中定义特定的返回类型:

1
2
3
4
5
6
7
8
9
interface iFoo
{
    public function bar (string $baz);
}

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}


用php7.3运行一些测试,当我这样做时,我不能让它抱怨(即使是严格的)。

1
2
3
4
5
6
7
interface A {
 function f() {}
}

interface B {
 function f():self {}
}

要么我的测试坏了,要么PHP改变了一些东西。一般来说,如果您减少可能的返回类型,那么不会破坏OOP。在任何使用该方法的类中,都可以处理任何返回,包括返回类型的子集。参数的情况大致相反。

他们在7.2中实现了这一点。


如果要从接口强制,该方法将返回对象,但对象类型不是接口类型,而是类本身,则可以这样编写:

1
2
3
4
5
6
7
interface iFoo {
    public function bar (string $baz) : object;
}

class Foo implements iFoo {
    public function bar (string $baz) : self  {...}
}

从php7.2开始就开始工作了。


在我看来,这是预期的行为。

只需更改你的Foo::bar方法返回iFoo,而不是self,就可以完成了。

说明:

接口中使用的self表示"iFoo类型的对象"。实现中使用的self表示"Foo类型的对象"。

因此,接口和实现中的返回类型显然不同。

其中一条评论提到Java和您是否会有这个问题。答案是肯定的,如果Java允许你编写这样的代码,你会遇到同样的问题,而不是这样。因为Java要求你使用类型的名称而不是PHP的EDCOX1×0的快捷方式,所以你永远看不到这一点。(请参见这里讨论Java中的类似问题)。