PHP 7 接口,返回类型提示和自我
Posted
技术标签:
【中文标题】PHP 7 接口,返回类型提示和自我【英文标题】:PHP 7 interfaces, return type hinting and self 【发布时间】:2016-12-28 09:10:21 【问题描述】:更新:php 7.4 现在does support covariance and contravariance 解决了这个问题中提出的主要问题。
我在 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 must be compatible with iFoo::bar(int $baz): iFoo in test.php on line 7
问题是 Foo 是 iFoo 的一个实现,所以据我所知,该实现应该与给定的接口完全兼容。我大概可以通过更改接口或实现类(或两者)来通过名称返回提示接口而不是使用self
来解决这个问题,但我的理解是语义上self
意味着“返回类的实例您刚刚调用了该方法”。因此,将其更改为接口在理论上意味着当我的意图是调用实例时,我可以返回实现接口的任何实例。
这是 PHP 中的疏忽还是经过深思熟虑的设计决定?如果是前者,是否有机会看到它在 PHP 7.1 中得到修复?如果不是,那么返回提示您的界面希望您返回刚刚调用该方法以进行链接的实例的正确方法是什么?
【问题讨论】:
我认为这是 PHP 返回类型提示中的错误,也许您应该将其提升为 bug;但是在这个后期阶段,任何修复都不太可能进入 PHP 7.1 由于 7.1 的最后一个 beta 版本已于几天前上线,因此任何修复都不太可能进入 7.1。 出于兴趣,您在哪里阅读您对self
返回类型应该如何工作的解释?
@Adam:self
的意思是“返回你调用它的实例,而不是其他实现相同接口的实例”似乎是合乎逻辑的。我似乎记得 Java 也有类似的返回类型(尽管我已经有一段时间没有进行任何 Java 编程了)
嗨,戈登。好吧,除非它被记录在某个地方可以工作,否则我不会指望什么可能是合乎逻辑的现实。 TBH 对于我所描述的情况,我会尽可能地声明并使用 iFoo 作为返回类型。是否存在实际上不起作用的情况? (我意识到这是“建议”/意见而不是“答案”。
【参考方案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);
3v4l:https://3v4l.org/VYbGn
原答案:
self
不引用实例,它引用当前类。接口无法指定必须返回相同的 instance - 以您尝试的方式使用 self
只会强制返回的实例属于同一类。
也就是说,PHP 中的返回类型声明必须是不变的,而您尝试的是协变的。
您对self
的使用相当于:
interface iFoo
public function bar (string $baz) : iFoo;
class Foo implements iFoo
public function bar (string $baz) : Foo ...
这是不允许的。
Return Type Declarations RFC 有this to say:
在继承期间声明的返回类型的强制执行是不变的;这意味着当子类型覆盖父方法时,子类型的返回类型必须与父方法完全匹配,并且不能省略。如果父级没有声明返回类型,则允许子级声明一个。
...
此 RFC 最初提出协变返回类型,但由于一些问题被更改为不变。将来可以添加协变返回类型。
目前至少你能做的最好是:
interface iFoo
public function bar (string $baz) : iFoo;
class Foo implements iFoo
public function bar (string $baz) : iFoo ...
【讨论】:
也就是说,我本以为static
的返回类型提示会起作用,但它甚至没有被识别
Paul,删除您在此处删除的 cmets 实际上是有害的,因为 (A) 它会丢失重要信息,并且 (B) 它会扰乱与其他 cmets 相关的讨论流程。我看不出有任何理由需要删除您依赖 Mark 和 Gordon 的 cmets。事实上,你到处都在这样做,它需要停止。绝对没有充分的理由回到一个年前的问题并删除所有的 cmets,从而完全破坏讨论的流程。事实上,它是有害且具有破坏性的。
这里看到的引用的一部分有一个重要的前言:“协变返回类型被认为是类型正确的,并且在许多其他语言中使用(C++ 和 Java但我相信不是 C#)。这个 RFC 最初提出协变返回类型,但由于一些问题被更改为不变。”。我很好奇 PHP 有什么问题。他们的设计选择会导致一些限制,这些限制也会导致特征出现奇怪的问题,在许多情况下使它们有些无用,除非您放松返回类型限制(如下面的一些答案所示)。非常令人沮丧。
@MarkBaker 返回类型 static
正在添加到 PHP 8。
也许是“最佳”选项,它也值得/** @return Foo */
(如果您无法升级您的 PHP 版本)。至少,IDE 会建议正确的类型提示。用 Netbeans 测试过。【参考方案2】:
如果你想从接口强制,那个方法会返回对象,但对象的类型不是接口的类型,而是类本身,那么你可以这样写:
interface iFoo
public function bar(string $baz): object;
class Foo implements iFoo
public function bar(string $baz): self ...
它从 PHP 7.4 开始工作。
【讨论】:
【参考方案3】:PHP 8 将添加“静态返回类型”来解决您的问题。
查看此 RFC:https://wiki.php.net/rfc/static_return_type
【讨论】:
【参考方案4】:这也可以是一个解决方案,你不在接口中明确定义返回类型,只在 PHPDoc 中,然后你可以在实现中定义特定的返回类型:
interface iFoo
public function bar (string $baz);
class Foo implements iFoo
public function bar (string $baz) : Foo ...
【讨论】:
或者用self
代替Foo
。【参考方案5】:
这在我看来是预期的行为。
只需将您的 Foo::bar
方法更改为返回 iFoo
而不是 self
即可。
解释:
接口中使用的self
表示“iFoo
类型的对象。”实现中使用的self
表示“Foo
类型的对象。”
因此,接口和实现中的返回类型显然不一样。
其中一个 cmets 提到了 Java,以及您是否会遇到这个问题。答案是肯定的,如果 Java 允许你编写这样的代码,你也会遇到同样的问题——但它不允许。因为 Java 要求你使用类型的名称而不是 PHP 的 @ 987654329@ 快捷方式,你永远不会真正看到这个。 (请参阅here,了解 Java 中一个类似问题的讨论。)
【讨论】:
所以是声明self
,类似于声明MyClass::class
?
@Laser 是的。
但是如果 Foo 实现了 iFoo 那么 Foo 就是 iFoo 类型的定义以上是关于PHP 7 接口,返回类型提示和自我的主要内容,如果未能解决你的问题,请参考以下文章