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


PHP 7 interfaces, return type hinting and self

UPDATE: 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必须与第7行test.php中的iFoo::bar(int $baz): iFoo兼容

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

这是PHP的疏忽还是故意设计的?如果是前者,是否有可能在PHP 7.1中修复它?如果不是,那么返回提示的正确方式是什么,你的接口希望你返回你刚刚调用了链接方法的实例?

编者按:下面的答案已经过时了。在php PHP7.4.0中,以下语句是完全合法的:

<?php
Interface I{
    public static function init(?string $url): self;
}
class C implements I{
    public static function init(?string $url): self{
        return new self();
    }
}
$o = C::init("foo");
var_dump($o);
  • 3 v4l: https://3v4l.org/VYbGn

原始回答:

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  {...}
}

它也可以是一个解决方案,你不明确定义返回类型的接口,只在PHPDoc,然后你可以定义特定的返回类型的实现:

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

PHP 8将添加"静态返回类型";这将解决你的问题。

查看这个RFC: https://wiki.php.net/rfc/static_return_type

这看起来像是我所期望的行为。

只需将Foo::bar方法更改为返回iFoo而不是self并完成它。

解释:

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

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

其中一条评论提到了Java,以及你是否会遇到这个问题。答案是肯定的,如果Java允许您编写这样的代码,那么您将遇到同样的问题——但它不允许。由于Java要求您使用类型的名称而不是PHP的self快捷方式,因此您永远不会真正看到这个。(请参阅此处查看Java中与类似的问题的讨论。)