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

更新:PHP 7.4 现在支持协方差和逆变解决了这个问题中提出的主要问题.

UPDATE: PHP 7.4 now does support covariance and contravariance which addresses the major issue raised in this question.

我在 PHP 7 中使用返回类型提示时遇到了一些问题.我的理解是提示 : self 意味着您打算让实现类返回自身.因此,我在接口中使用了 : self 来表明这一点,但是当我尝试实际实现接口时,我遇到了兼容性错误.

I have run into something of an issue with using return type hinting in PHP 7. My understanding is that hinting : self means that you intend for an implementing class to return itself. Therefore I used : self in my interfaces to indicate that, but when I tried to actually implement the interface I got compatibility errors.

以下是我遇到的问题的简单演示:

The following is a simple demonstration of the issue I've run into:

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 Fatal error: Declaration of Foo::bar(int $baz): Foo must be compatible with iFoo::bar(int $baz): iFoo in test.php on line 7

PHP Fatal error: Declaration of Foo::bar(int $baz): Foo must be compatible with iFoo::bar(int $baz): iFoo in test.php on line 7

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

The thing is Foo is an implementation of iFoo, so as far as I can tell the implementation should be perfectly compatible with the given interface. I could presumably fix this issue by changing either the interface or the implementing class (or both) to return hint the interface by name instead of using self, but my understanding is that semantically self means "return the instance of the class you just called the method on". Therefore changing it to the interface would mean in theory that I could return any instance of something that implements the interface when my intent is for the invoked instance is what will be returned.

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

Is this an oversight in PHP or is this a deliberate design decision? If it's the former is there any chance of seeing it fixed in PHP 7.1? If not then what is the correct way of return hinting that your interface expects you to return the instance you just called the method on for chaining?

推荐答案

编者注:以下答案已过时.作为 php PHP7.4.0,以下是完全合法的:

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

  • 3v4l:https://3v4l.org/VYbGn
  • 原答案:

    self 不是指实例,而是指当前类.接口无法指定必须返回相同的 instance - 以您尝试的方式使用 self 只会强制返回的实例属于同一班.

    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.

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

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

    你对self的使用相当于:

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

    这是不允许的.

    返回类型声明 RFC 有 这个要说:

    继承时声明的返回类型的执行是不变的;这意味着当子类型覆盖父方法时,子类型的返回类型必须与父方法完全匹配并且不能省略.如果父级未声明返回类型,则允许子级声明返回类型.

    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.

    ...

    此 RFC 最初提出协变返回类型,但由于一些问题改为不变.将来可以添加协变返回类型.

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

相关文章