Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP 7.1: Is there a native interface that matches "string" and "Object that implements __toString()"?

We have objects that implement __toString():

class Foo {
    public function __toString()
    {
        return 'bar';
    }
}

Then we have functions that return either string or object that implement __toString(), like the example above.

While using return type string for strings works of course:

function getString():string {
    return 'Works!';
}

Returning object of type Foo does not work, because it's not a string:

function getString(Foo $foo):string {
    return $foo; // Fatal TypeError!
}

Is there any PHP interface that we can use to type hint both string and objects that implement that unknown interface, e.g. Printable or Stringable (lol)?

The goal is to being able to return either string or objects that implement a specific interface (which would enforce the implementation of __toString).

like image 655
Lionel Avatar asked Nov 14 '17 12:11

Lionel


1 Answers

The simple answer is "no" - there is no built-in "stringable" hint, although it has occasionally been suggested in the past.

As of PHP 8.0, there will be support for "union types", so string|Stringable would represent "either a string or an instanceof Stringable". However, there's no such interface as Stringable, so this can't be used to detect implementations of __toString.

Part of the reason for this, and the design question you need to ask, is what contract are you actually representing here? From the point of view of the calling code, there is no guarantee that the return value can actually be used as a string directly - there are many operations which will behave differently for a string and an object, or which will accept a string but not cast an object automatically. So the only thing that's actually guaranteed by such a hint is that (string)$foo will give a string.

So you have a few options:

  • As Stefan says, always return an object, implementing some interface Stringable. If the function wants to return a string, it would first wrap it in a simple object.
  • Instead of a generic Stringable, come up with a more meaningful interface to return; maybe what you actually return is something Loggable, for instance. This way, wrapping the string can give you more concrete benefits: you can add other methods to the interface, and the calling code doesn't need to do an "object or string" check before calling those methods.
  • If the calling code is only ever going to use the return value as a string, don't return the object at all. Mark the function as returning string, and cast any objects to string before returning them.
  • If the calling code is expected to inspect the return value and do different things with different kinds of objects, don't limit the return type at all. In PHP 8, you might want to add a union type, but only if there's a short list of possibilities; a return type that's too broad is barely useful as a contract anyway.
like image 118
IMSoP Avatar answered Sep 22 '22 06:09

IMSoP