简体   繁体   English

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

[英]PHP 7 interfaces, return type hinting and self

UPDATE : PHP 7.4 now does support covariance and contravariance which addresses the major issue raised in this question.更新:PHP 7.4 现在确实支持协变和逆变,这解决了这个问题中提出的主要问题。


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.我在 PHP 7 中使用返回类型提示时遇到了一些问题。我的理解是提示: self意味着您打算实现 class 以返回自身。 Therefore I used : self in my interfaces to indicate that, but when I tried to actually implement the interface I got compatibility errors.因此,我在我的界面中使用: self来表示这一点,但是当我尝试实际实现该界面时,我遇到了兼容性错误。

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

The expected output was:预期的 output 是:

Fred Wilma Barney Betty弗雷德·威尔玛·巴尼·贝蒂

What I actually get is:我实际得到的是:

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 致命错误:Foo::bar(int $baz) 声明:Foo 必须与 iFoo::bar(int $baz) 兼容:第 7 行的 test.php 中的 iFoo

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.问题是 Foo 是 iFoo 的一个实现,据我所知,这个实现应该与给定的接口完全兼容。 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".我大概可以通过更改接口或实现 class(或两者)来通过名称而不是使用self返回提示接口来解决这个问题,但我的理解是语义上的self意味着“返回你刚刚调用的 class 的实例方法”。 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.因此,将其更改为接口在理论上意味着我可以返回实现该接口的任何实例,而我的意图是调用的实例将被返回。

Is this an oversight in PHP or is this a deliberate design decision?这是 PHP 中的疏忽还是故意的设计决定? If it's the former is there any chance of seeing it fixed in PHP 7.1?如果是前者,是否有机会在 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?如果不是,那么正确的返回方式是什么,暗示您的接口希望您返回您刚刚调用方法进行链接的实例?

self does not refer to the instance, it refers to the current class. self不是指实例,而是指当前类。 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.接口无法指定必须返回相同的实例- 以您尝试的方式使用self只会强制返回的实例属于同一类。

That said, return type declarations in PHP must be invariant while what you're attempting is covariant.也就是说,PHP 中的返回类型声明必须是不变的,而您正在尝试的是协变的。

Your use of self is equivalent to:你对self使用相当于:

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 :返回类型声明 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.如果父级未声明返回类型,则允许子级声明返回类型。

... ...

This RFC originally proposed covariant return types but was changed to invariant because of a few issues.该 RFC 最初提出协变返回类型,但由于一些问题改为不变。 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  {...}
}

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:它也可以是一个解决方案,您没有在接口中明确定义返回类型,只在 PHPDoc 中定义,然后您可以在实现中定义某些返回类型:

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

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

In case, when You want to force from interface, that method will return object, but type of object won't be the type of interface, but the class itself, then You can write it this way:如果你想从接口强制,该方法将返回对象,但对象的类型不会是接口的类型,而是类本身,那么你可以这样写:

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

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

It's working since PHP 7.4.它从 PHP 7.4 开始工作。

PHP 8 will add "static return type" which will solve your problem. PHP 8 将添加“静态返回类型”,这将解决您的问题。

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

Running some tests with PHP 7.3, I can't get it to complain (even with strict) when I do this... 使用PHP 7.3运行一些测试,当我这样做时,我不能让它抱怨(即使是严格的)...

interface A {
 function f() {}
}

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

Either my test has broken or PHP has changed some things. 要么我的测试破坏了,要么PHP已经改变了一些东西。 Generally speaking, if you reduce the possible return types, that tends to not break OOP. 一般来说,如果减少可能的返回类型,那么往往不会破坏OOP。 As in any class using that method can handle any return, including a subset of return types. 在任何使用该方法的类中都可以处理任何返回,包括返回类型的子集。 The opposite is roughly the case for parameters. 相反的情况与参数大致相同。

They implemented this in 7.2. 他们在7.2中实现了这一点。

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.只需更改您的Foo::bar方法以返回iFoo而不是self并完成它。

Explanation:说明:

self as used in the interface means "an object of type iFoo ."接口中使用的self表示“类型为iFoo的对象”。
self as used in the implementation means "an object of type Foo ." self中使用的self表示“类型为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.其中一条评论提到了 Java 以及您是否会遇到此问题。 The answer is yes, you would have the same issue if Java allowed you to write code like that -- which it doesn't.答案是肯定的,如果 Java 允许您编写这样的代码,您会遇到同样的问题——但事实并非如此。 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.由于 Java 要求您使用类型的名称而不是 PHP 的self快捷方式,因此您永远不会真正看到这一点。 (See here for a discussion of a similar issue in Java.) (有关 Java 中类似问题的讨论,请参见此处。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM