PHP 7接口,返回类型提示和自我


88

更新:PHP 7.4现在确实支持协方差和逆方差,这解决了此问题中提出的主要问题。


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

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

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");

预期输出为:

弗雷德·威尔玛·巴尼·贝蒂

我真正得到的是:

PHP致命错误:Foo :: bar(int $ baz)的声明:Foo必须与iFoo :: bar(int $ baz):第7行的test.php中的iFoo兼容

事情是Foo是iFoo的实现,据我所知,该实现应该与给定的接口完全兼容。我大概可以通过更改接口或实现类(或两者)以按名称而不是使用来返回提示接口来解决此问题self,但我的理解是从语义上讲self意味着“返回您刚刚在其上调用方法的类的实例” ”。因此,从理论上讲,将其更改为接口意味着我可以返回任何实现接口的东西的实例,而当我的意图是要调用的实例是要返回的对象时。

这是PHP的疏忽,还是故意的设计决策?如果是前者,有没有机会看到它在PHP 7.1中修复?如果不是,那么正确的返回方式暗示您的接口希望您返回刚刚调用该方法进行链接的实例?


我认为这是PHP返回类型提示中的错误,也许您应该将其作为bug提出;但任何修补程序都不太可能在此后期进入PHP 7.1
Mark Ba​​ker

由于7.1的最新beta版本几天前已经上线,因此几乎不可能将任何修补程序纳入7.1。
夏洛特·杜诺瓦

出于兴趣,您在哪里阅读有关self返回类型应如何工作的解释?
亚当·卡梅隆

@Adam:似乎很合理的self意思是“返回调用此实例的实例,而不是其他实现相同接口的实例”。我似乎记得Java具有类似的返回类型(尽管自从我进行Java编程以来已经有一段时间了)
GordonM

1
嗨,戈登。好吧,除非有文档证明它可以在某个地方工作,否则我不会指望现实中合乎逻辑的东西。TBH在我要描述的情况下,我将尽可能声明性地使用iFoo作为返回类型。是否存在无法实际使用的情况?(我知道这是“建议” /意见,而不是“答案”,那说。
亚当卡梅伦

Answers:


94

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

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

您对的使用self等同于:

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

class Foo implements iFoo
{

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

这是不允许的。


返回类型声明RFC这样一段话

继承过程中声明的返回类型的执行是不变的;这意味着当子类型覆盖父方法时,子的返回类型必须与父方法完全匹配,并且不能省略。如果父级未声明返回类型,则允许子级声明一个返回类型。

...

该RFC最初提出了协变返回类型,但由于一些问题,已更改为不变。将来可能会添加协变返回类型。


暂时至少可以做的是:

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

class Foo implements iFoo
{

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

18
话虽如此,我原本希望返回的提示类型static能够起作用,但是甚
Mark Ba​​ker

我认为情况就是如此,这就是我要解决的问题。但是,如果可能的话,我宁愿只使用:self,因为如果一个类正在实现一个接口,那么self的返回值也隐含地也是该接口实例的返回值。
GordonM

19
保罗,删除您在此处删除的评论实际上是有害的,因为(A)它丢失了重要信息,并且(B)它破坏了相对于其他评论的讨论流程。我看不出有任何理由需要删除您依赖Mark和Gordon的评论。实际上,您到处都在这样做,并且需要停止。绝对没有充分的理由要回顾一个已有很长时间的问题并删除您的所有评论,从而完全破坏讨论的流程。实际上,这是有害和破坏性的。
科迪·格雷

有一段重要的序言可以在其中看到:“协变返回类型被认为是声音类型,并且在许多其他语言(C ++和Java,但我认为不是C#)中使用。此RFC最初提出了协变返回类型,但由于一些问题而被更改为不变式。”。我很好奇PHP有什么问题。他们的设计选择会引起一些局限性,这些局限性还会导致特质出现奇特的问题,在许多情况下,除非您放松返回类型的约束,否则它们在某种程度上就毫无用处(如以下一些答案所示)。非常沮丧。
John

1
@MarkBaker返回类型static被添加到PHP 8.
里卡多首领

16

这也可以是一个解决方案,您不必在接口中仅在PHPDoc中明确定义返回类型,然后可以在实现中定义特定的返回类型:

interface iFoo
{
    public function bar (string $baz);
}

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

2
或者,而不是Foo仅仅使用self
而是

2

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

interface iFoo {
    public function bar (string $baz) : object;
}

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

从PHP 7.4开始就可以使用。



0

这看起来对我来说是预期的行为。

只要你改变Foo::bar的方法来回报iFoo,而不是self与它来完成。

说明:

self接口中使用的“表示类型的对象” iFoo
self在实现中使用的含义是“类型的对象” Foo

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

评论之一提到Java以及您是否会遇到此问题。答案是肯定的,如果Java允许您编写类似的代码,您将遇到同样的问题,而事实并非如此。由于Java要求您使用类型的名称而不是PHP的self快捷方式,因此您永远不会真正看到它。(有关Java类似问题的讨论,请参见此处。)


声明self类似于声明 MyClass::class吗?
peterchaula

1
@激光是的。
Moshe Katz

4
但是,如果Foo实现了iFoo,那么Foo就是iFoo类型的定义
GordonM '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.