static return type in PHP 7 interfaces
Asked Answered
E

2

23

Why is it not possible in PHP 7, to declare an interface with a static return type?

Let's say I have the following classes:

interface BigNumber {
    /**
     * @param BigNumber $that
     *
     * @return static
     */
    public function plus(BigNumber $that);
}

class BigInteger implements BigNumber { ... }
class BigDecimal implements BigNumber { ... }

I want to enforce the return type of the plus() method to static, that is:

  • BigInteger::plus() must return a BigInteger
  • BigDecimal::plus() must return a BigDecimal

I can declare the interface in the following way:

public function plus(BigNumber $that) : BigNumber;

But that doesn't enforce the above. What I would like to do is:

public function plus(BigNumber $that) : static;

But PHP 7, to date, is not happy with it:

PHP Parse error: syntax error, unexpected 'static' (T_STATIC)

Is there a specific reason for this, or is this a bug that should be reported?

Edwinedwina answered 20/6, 2015 at 10:21 Comment(1)
Type invariance, that's why. Implementations / overriding methods must match the type exactly in PHP; static doesn't - obviously, because it refers to current context and therefore can not be invarianced.Concave
E
18

2020 update

Static return types have been introduced in PHP 8.

Edwinedwina answered 31/1, 2020 at 13:49 Comment(0)
A
8

It's not a bug, it just doesn't make sense design-wise from an object-oriented programming perspective.

If your BigInteger and BigDecimal implement both BigNumber, you care about the contract they fulfil. I this case, it's BigNumber's interface.

So the return type you should be using in your interface is BigNumber since anybody coding against that interface does not know anything else than members of that interface. If you need to know about which one is returned, the interface is perhaps too wide in the first place.

Note: programming languages with generics can achieve this effect by specifying the return type as the generic type, but PHP does not have generics and probably will not have in the near future.

Abramabramo answered 20/6, 2015 at 10:26 Comment(5)
My idea was to be flexible on the input (you can compare a BigInteger with a BigDecimal for example), but strict on the output (whatever method called on a class should return an instance of the same class). Maybe that doesn't make total sense, but at least PHPdoc allows this kind of usage (@return static)!Edwinedwina
Well PHPDoc actually doesn't know anything about static phpdoc.org/docs/latest/references/phpdoc/types.html, the closest is self which in this case is identical to the interface return type hint.Abramabramo
Weird, this one: phpdoc.org/docs/latest/guides/types.html actually documents static: "an object of the class where this value was consumed, if inherited it will represent the child class. (see late static binding in the PHP manual)."Edwinedwina
Originally I was of the belief that self as a return type is subtly different to specifying the full class name -- I thought it was a contract to return the exact same instance of the class you were accessing. However it seems that in the context of Type Declarations, self is simply a shorthand for the full class name. This seems like an unfortunate design decisions since self in the context of a method body is a reference to the class itself.Strachey
I can find at very least one 'legit' usage - I am creating abstract model class, and I have find function. If let's say there is UserModel derriving AbstractModel, I have find logic in AbstractModel. That logic depends on something implemented in UserModel (ie. UserModel name). Now, I not only want UserModel::find() to return instance of UserModel, I need some details of UserModel definition. And that way, there is no logical way to make let's say UserModel::find() return PostModel which contains another abstract static function that configures abstract model for proper table.Ossiferous

© 2022 - 2024 — McMap. All rights reserved.