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

时间:2016-08-21 21:21:02

标签: php interface return-type php-7 type-hinting

我在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中修复?如果没有,那么正确的返回提示是什么,你的接口希望你返回刚刚调用方法的实例进行链接的正确方法是什么?

6 个答案:

答案 0 :(得分:66)

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

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

您对self的使用等同于:

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

class Foo implements iFoo
{

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

是不允许的。

Return Type Declarations RFCthis to say

  

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

     

...

     

此RFC最初提出了协变返回类型,但由于一些问题而被更改为不变量。可以在将来的某个时候添加协变返回类型。

目前至少你可以做的最好的事情是:

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

class Foo implements iFoo
{

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

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:1)

PHP 8将添加“静态返回类型”,这将解决您的问题。

查看以下RFC:https://wiki.php.net/rfc/static_return_type

答案 3 :(得分:0)

这看起来像我预期的行为。

只需更改Foo::bar方法即可返回iFoo而不是self,并完成此操作。

说明:

接口中使用的

self表示“iFoo类型的对象。”
实现中使用的self表示“Foo类型的对象。”

因此,界面中的返回类型和实现明显不一样。

其中一条评论提及Java以及您是否会遇到此问题。答案是肯定的,如果Java允许您编写类似的代码,那么您将遇到同样的问题因为Java要求您使用该类型的名称而不是PHP的{ {1}}捷径,你永远不会真正看到这一点。 (有关Java中类似问题的讨论,请参阅here。)

答案 4 :(得分:0)

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

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

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

自PHP 7.2起有效。

答案 5 :(得分:0)

使用PHP 7.3运行一些测试,这样做时我不会抱怨(甚至严格)...

interface A {
 function f() {}
}

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

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

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