提问者:小点点

PHP7接口,返回类型提示和self


更新:PHP7.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):iFoo在第7行test.php

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

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


共3个答案

匿名用户

编者按:下面的答案是过时的。

<?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);
  • 3v4l:https://3v4l.org/VYbGn

原始答复:

不引用实例,它引用当前类。接口没有办法指定必须返回相同的实例——以您尝试的方式使用Self只会强制返回的实例属于同一类。

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

你对自己的使用相当于:

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

匿名用户

在这种情况下,当您想从接口强制时,该方法将返回object,但object的类型不是接口的类型,而是类本身,然后您可以这样编写:

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

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

它从PHP 7.4开始工作。