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

更新: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): iFoo 在测试中.php在第 7 行

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

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


答案 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);

原答:

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

答案 2

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

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

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

推荐