Re: [RFC] Adding a "Stringable" interface to PHP 8

  108539
February 13, 2020 15:04 patrickallaert@php.net (Patrick ALLAERT)
Hi,

> allow using string|Stringable to express string|object-with-__toString()
That goal is expressed in a technical way, rather than a functional one. I have the feeling that addressing the goal: "Can I represent/cast variable $str as a string" would address a broader, yet much more useful, scope. string|Stringable: 1. Denotes a list of types, not a capability as "stringable" would as a virtual type (like iterable). It somewhat contradicts the principle of knowing the *type* vs knowing what we can do with it. 2. Is incomplete in terms of what can be "represented as a string". The Stringable interface used alone tells there is a __toString() method. Therefor, we should expect it to be used to call the __toString() method explicitly: function test(Stringable $object) { \strtoupper($object->__toString();) // do something with __toString(); } .... in order to be consistent with what interfaces are meant. If mainly used for string casting capability, then it will only match objects and not all the other types that can be casted as string. A virtual "stringable" type (that would be similar to "iterable)" would, IMHO, be of a bigger benefit if it is to denote the fact that "a variable can be transformed to a string". By design, when a variable is declared as "iterable", you know you can foreach() on it now, but also on other yet-to-be-implemented concepts that would support foreach looping. Think about it as if generators would have appeared after "iterable" keyword and then, added to it.
> Adding a new stringable special type (like iterable, callable, etc.) is not considered in this RFC because it would require adding a new reserved
keyword in the language. This would break BC more heavily and would defeat goal #2 mentioned previously (ability to polyfill on PHP7.) It's perfectly fine to add a new reserved keyword in the language, and a major version is absolutely the best moment for it. Not sure how much more break would be implied by a keyword, vs the interface. As it is presented, Stringable alone seems of very little use as compared to union like "string|Stringable", so using the polyfill in a version of PHP that does not support union does not seem to make sense. What is the real advantage of adding this to the core, rather than some FIG/PSR standard? This would be compatible in both current versions of PHP and next ones at the same time. This RFC would also introduce a very new concept, which seems nice at a first glance, but a bit nasty too: class Foo { /** @deprecated */ public string __toString() { throw new \Exception("Foo should not be casted to string, you should now..."); } } The above would imply that "Foo implements Stringable" unconditionally: without opt-in, but also without opt-out! It would also be the only one case of classes declared without "implements" that would still implement an interface: quite contradictory and hacky! It would be less "hacky" if a broader concept (let's say "Signature"?) would exist for checking at runtime the availability of a method without requiring an interface to be implemented, e.g.: Option 1: By introducing the "signature" keyword: signature CanCommit { public function commit(); } used like this: if ($object instanceof CanCommit) { $object->commit(); } Option 2: By introducing an extra meaning to keyword "implements": interface CanCommit { public function commit(); } used like this: if ($object implements CanCommit) { // Checking at runtime that $object's class would comply to interface CanCommit $object->commit(); } If this, or a similar concept would exist, one could just declare a signature for __toString() and we wouldn't have the incoherence that this RFC introduces. Another, also broader, approach would be to implement a global way to check "castability" to type X: function foo((string) $stringable) { // Type not enforced, but possibility of casting checked } I'm aware that this RFC addresses a small goal, but accepting lot of small changes compared to bigger ones with a broader vision could lead to even more inconsistencies in the core in the longer run. Regards, Patrick
  108545
February 13, 2020 17:16 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Hi Patrick,

thanks for taking the time to explain your vote.


A virtual "stringable" type (that would be similar to "iterable)" would,
> IMHO, be of a bigger benefit if it is to denote the fact that "a variable > can be transformed to a string". > By design, when a variable is declared as "iterable", you know you can > foreach() on it now, but also on other yet-to-be-implemented concepts that > would support foreach looping. Think about it as if generators would have > appeared after "iterable" keyword and then, added to it. >
I don't make a difference between a type and what you call a virtual type: any type, virtual or not, is some guarantee that a variable can be used is some specific way, typically because some methods exist in the object or something else for special types managed by the engine. As such, any type implies a behavior. My understanding of the existing composite types is that they were added because PHP lacked union types. We could imagine a future where PHP will support type aliases. That day, iterable could be strictly equivalent to array|Traversable|Iterator|ItratorAggregate and everything would be fine. PHP doesn't need magic types as a feature of the language to provide any special behaviors. Of course, this is my interpretation, and the RFC builds on it. What is the real advantage of adding this to the core, rather than some
> FIG/PSR standard? This would be compatible in both current versions of PHP > and next ones at the same time. >
At least the auto and implicit declaration of the interface + the return type, that makes a big diff.
> public string __toString() { > throw new \Exception("Foo should not be casted to string, you > should now..."); > } >
This concern exists with any interface actually, nothing specific to this one. As the vote is open now, I'm going to keep it going until the end. If it does not pass - or if it passes and you feel strongly about it, we might want to submit an RFC on top of course. Thanks again for your explanation, Nicolas