更新: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中是否有机会修复它?如果不是,那么什么是正确的返回方式,暗示您的接口希望您返回刚刚调用该方法进行链接的实例?
编者按:下面的答案是过时的。
<?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
只会强制返回的实例属于同一类。
也就是说,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开始工作。