phpoopinterfacereturn-typephp-internals

What is the reasoning behind the refusal of PHP to accept the return types in this simple situation?


In PHP 7.1.4, using strict typing, I have a simple object oriented setup involving some interfaces, and some classes implementing those interfaces. Below example, as you would expect, works fine.

declare(strict_types=1);

interface Loginable {
  public function login();
}

interface Upgradeable {
  public function upgrade(): Loginable;
}

class Person implements Upgradeable {
  function upgrade(): Loginable {
    return new PersonAccount();
  }
}

class PersonAccount implements Loginable {
  public function login() {
    ;
  }
}

Notice how the upgrade function inside the Upgradable interface requires a Loginable return type, which is another interface. The upgrade method inside the Person class, in this example, specifies a Loginable interface as its return type to match the stipulation of the interface.

However, if I now attempt to specify the return type of the upgrade method of the Person class more accurately, I run into a fatal error.

class Person implements Upgradeable {
  function upgrade(): PersonAccount {
    return new PersonAccount();
  }
}

Please observe that what I am trying to accomplish here is to specify that the upgrade method will return an object that implements the interface that is the required return type according to the interface implemented by the class. This seems very logical and correct to me, however, PHP will say:

Fatal Error: Declaration of Person::upgrade(): PersonAccount must be compatible with Upgradeable::upgrade(): Loginable in [...]

As it has already been pointed out by yivi at https://stackoverflow.com/a/49353076/9524284 what I am trying to accomplish is not possible.

I would accept the complaint of PHP if there were extended classes involved, because the extending class could override the original method, and that way there would be no guarantee of the correct return type. However, in the scenario described above, classes are not extended. There are only implementations of interfaces, the whole purpose of which is to explicitly guarantee the correct implementation.

Please shed some light on the reasoning behind the refusal of PHP to accept the above described way of declaring return types!


Solution

  • You're describing a type-reasoning feature called covariance, which is itself a consequence of the Liskov Substitution Principle.


    As of PHP 7.4, this works as you expect. (See the RFC implementing the behavior for details.)


    Prior to then, this was discussed on internals. As was stated in one such conversation:

    if an implementation better than satisfies the requirements defined by an interface, it should be able to implement that interface.

    So, yes, PHP should allow covariance as you describe. But realize: it's not that PHP refuses to implement covariance, it's that PHP has not yet implemented covariance. There are some technical hurdles to doing so, but they're not insurmountable. As was stated in that same thread on internals by a core maintainer:

    It's doable, it just hasn't been done.

    If you'd like to produce an RFC and a PR, please do so. Until then, it's just an unfortunate status quo of the ever-evolving PHP object system.