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:
- 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. - 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
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