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

更新 : PHP 7.4 now 确实支持反变解决了这个问题中提出的主要问题。


在 PHP7中使用返回类型提示遇到了一些问题。我的理解是,提示 : 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)兼容

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

这是 PHP 中的一个疏忽,还是一个深思熟虑的设计决策?如果是前者,有没有可能在 PHP 7.1中修复它?如果没有,那么返回提示的正确方式是什么,提示您的接口期望您返回刚才为链接调用该方法的实例?

59255 次浏览

editorial note: the answer below is outdated. as php PHP7.4.0, the following is perfectly legal:

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

original answer:

self does not refer to the instance, it refers to the current class. There is no way for an interface to specify that the same instance must be returned - using self in the manner you're attempting would only enforce that the returned instance be of the same class.

That said, return type declarations in PHP must be invariant while what you're attempting is covariant.

Your use of self is equivalent to:

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


class Foo implements iFoo
{


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

which is not allowed.


The Return Type Declarations RFC has this to say:

The enforcement of the declared return type during inheritance is invariant; this means that when a sub-type overrides a parent method then the return type of the child must exactly match the parent and may not be omitted. If the parent does not declare a return type then the child is allowed to declare one.

...

This RFC originally proposed covariant return types but was changed to invariant because of a few issues. It is possible to add covariant return types at some point in the future.


For the time being at least the best you can do is:

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


class Foo implements iFoo
{


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

This looks like the expected behavior to me.

Just change your Foo::bar method to return iFoo instead of self and be done with it.

Explanation:

self as used in the interface means "an object of type iFoo."
self as used in the implementation means "an object of type Foo."

Therefore, the return types in the interface and the implementation are clearly not the same.

One of the comments mentions Java and whether you would have this issue. The answer is yes, you would have the same issue if Java allowed you to write code like that -- which it doesn't. Since Java requires you to use the name of the type instead of PHP's self shortcut, you are never going to actually see this. (See here for a discussion of a similar issue in Java.)

It also can be a solution, that you don't define explicitly the return type in the Interface, only in the PHPDoc and then you can define the certain return type in the implementations:

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


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

PHP 8 will add "static return type" which will solve your problem.

Check out this RFC: https://wiki.php.net/rfc/static_return_type