[Concept] Magic Casting

php.internals

Alex Bowers

9 years ago
Hello All, In PHP we currently have the ability to type hint classes in method signatures, however, sometimes it would be useful to convert the items to a different instance / type. An example is collections and arrays. If implemented properly, an array and a collection could be used interchangeably, although a collection may be preferred. For consistency, having the ability to dynamically cast from an array to a collection would be very useful. An idea of how this may look would be: <?php class Collection extends ... { protected $items; public function __construct(array $items) { $this->items = $items; } ... public function __cast(array $items) : Collection { return new static($items) } }
-- The __cast method MUST return an instance of itself, so that when called like so: function convert_me(Collection $collection) { var_dump($collection); } convert_me([1,2,3]); the result there would be a Collection instance, instead of an array. If the type passed is not accepted by __cast() or __cast() throws an exception, then the current errors of invalid type are thrown, or perhaps the custom exception message. I'm not sure if this has been proposed before, and if it has, please could someone assist in me finding it, I couldn't find it by a quick search on the wiki.php.net/rfc page. Thanks. Alex.

Alex Bowers

9 years ago
Sorry, forgot a little bit of information. If the type passed is already satisfactory (an instance), then __cast is NOT called.

Alex Bowers

9 years ago
And because the email formatting appears bad (at least in externals.io) Here it is in a gist: https://gist.github.com/alexbowers/9520c8df746249ecae2d9c7aad2e54ae

David Rodrigues

9 years ago
var_dump() should uses __toString() here, not? 2016-12-02 11:44 GMT-02:00 Alex Bowers <bowersbros@gmail.com>:
> And because the email formatting appears bad (at least in externals.io) > > Here it is in a gist: > https://gist.github.com/alexbowers/9520c8df746249ecae2d9c7aad2e54ae
-- David Rodrigues

Andrey Andreev

9 years ago
Hi, On Fri, Dec 2, 2016 at 3:44 PM, Alex Bowers <bowersbros@gmail.com> wrote:
> And because the email formatting appears bad (at least in externals.io) > > Here it is in a gist: > https://gist.github.com/alexbowers/9520c8df746249ecae2d9c7aad2e54ae >
There's been a few times I've wanted something like that, but I don't really like the idea of yet another magic method. Especially given the obvious limitation of just one parameter being accepted, the whole thing may work with just an interface: interface Castable { public function __construct($whatever); } Cheers, Andrey.

Alex Bowers

9 years ago
Sorry for dupe, hit reply not reply-all. I don't see how the interface is equivalent. The benefit of this, is that you can convert types passed into a method to the type you expect automagically, Castable wouldn't allow that, only a new magic method (or reflection and user land code possibly?) would allow this. As for the limitation of only one parameter being accepted, what would the other possible parameters be? Since this is based entirely on the casted variable, there can only possibly be one variable there, the one passed into that position in the functions arguments. Arguably, this feature would resolve some bugs in peoples code, because it can allow better enforcing of types. Currently, a place that accepts a Collection would not be able to type hint it if it wants to also accept an array, instead it would have to check and change inside the method, but that shouldn't be the role of the method. The method just wants to assume, and work on the assumption that it is the correct type, but without losing flexibility. The ability to cast to your own type automatically would help with this assumption, as now everywhere would be able to assume Collection, and use it as so, but allow the current flexibility of arrays being passed in also.

Ivan Enderlin @ Hoa

9 years ago
Hello :-), Casting is already a very large surface of unsafety and bugs. Adding “magic” before this is like multiplying the surface by infinite. The only goal I see here is to save a `new Collection(…)`, and this is not a sufficient reason from my point of view. If `array` was a type, and `IntoCollection` a trait, implemented for the `array` type, then we could do things like `[1, 2, 3].intoCollection()` for instance. However PHP does not bring this kind of features. So let's reverse this: `Collection::fromArray([1, 2, 3])`. But wait, this is exactly what `new Collection([1, 2, 3])` does for this specific case. Do you have better use cases? A casting comes with a non-negligeable cost. You have to define your data clearly, and avoid casting as much as possible. Cheers. On 02.12.16 14:37, Alex Bowers wrote:

Rowan Collins

9 years ago
On 2 December 2016 13:37:20 GMT+00:00, Alex Bowers <bowersbros@gmail.com> wrote:
>Hello All, > >In PHP we currently have the ability to type hint classes in method >signatures, however, sometimes it would be useful to convert the items >to a >different instance / type. > >An example is collections and arrays. If implemented properly, an array >and >a collection could be used interchangeably, although a collection may >be >preferred. For consistency, having the ability to dynamically cast from >an >array to a collection would be very useful.
I think there are really two different features wrapped up here. First, allowing user defined classes to define casting behaviour to or from other types. This has certainly been discussed in different forms over the years, although mostly in terms of casting *to* another type (e.g. a generalisation of __toString). One of the big challenges is that without method overloading, how do you nicely declare two different casts, to or from different types? The generalised case also suffers from the problem of multiple despatch - do you call Foo::fromBar() or Bar::toFoo()? The second part is making that casting automatic when a type hint is encountered. I can sort of understand this as a generalisation of coercive type hints on scalars, but it feels like it could get rather hard to debug. It seems easier to read in your example to just type hint the collection, and make the user manually create one: function whatever(Collection $c) { ... } $a = array( ... ); whatever( Collection::fromArray($a) ); Explicit casts would take you from there to: function whatever(Collection $c) { ... } $a = array( ... ); whatever( (Collection)$a ); Either of these is more typing than the cast being implicitly added by the engine, but it's clearer that there's some conversion happening. Imagine if the cast is an expensive operation - calling it explicitly over and over again would be an obvious clue to refactor, but having it silently called repeatedly might go unnoticed. Regards,
-- Rowan Collins [IMSoP]