Hey everyone,
I want to get a feel for something I would like to implement into PHP core.
I would like to be able to cast objects to my own class types. Similar to
already available PHP syntax:
$foo = (string) $bar;
The syntax would look like this:
$foo = (\My\FooObject) $bar;
It would also support:
$foo = ( (\My\FooObject) $bar )->myFooObjectFunction();
I'm still trying to decide what would happen internally and I would like
some feedback. I'm thinking of 3 different things it could do:
-
Directly change the class type, functionally equivalent to:
function castType($object, $type) {
return unserialize(preg_replace("/^O:[0-9]+:"[^"]+":/i",
"O:".strlen($type).":"$type":", serialize($object)));
} -
Throw a fatal error if the object doesn't match the type trying to be
cast. -
Add a new static magic method called __cast() that would allow the
developer to decide what happens. Like this:
namespace \My;
class FooObject {
public static function __cast($foo) {
// Do stuff here
return new FooObject;
}
}
I'm willing to implement this myself if everybody else likes this idea.
Thanks!
Chris London
Hey everyone,
I want to get a feel for something I would like to implement into PHP core.
I would like to be able to cast objects to my own class types. Similar to
already available PHP syntax:$foo = (string) $bar;
The syntax would look like this:
$foo = (\My\FooObject) $bar;
It would also support:
$foo = ( (\My\FooObject) $bar )->myFooObjectFunction();
I'm still trying to decide what would happen internally and I would like
some feedback. I'm thinking of 3 different things it could do:
Directly change the class type, functionally equivalent to:
function castType($object, $type) {
return unserialize(preg_replace("/^O:[0-9]+:"[^"]+":/i",
"O:".strlen($type).":"$type":", serialize($object)));
}Throw a fatal error if the object doesn't match the type trying to be
cast.Add a new static magic method called __cast() that would allow the
developer to decide what happens. Like this:namespace \My;
class FooObject {
public static function __cast($foo) {
// Do stuff here
return new FooObject;
}
}I'm willing to implement this myself if everybody else likes this idea.
Thanks!Chris London
Hi
Do you have any use cases why this would be useful?
IMO an object is only meant to do what it is created for.
Converting objects to different types can have very unexpected results.
If you want to reuse code, you can just use traits, or you can use
dependency injection to get functionality of one object inside another.
Also what would happen if you cast objects to invalid types?
E.G if you convert a person entity to a product entity, which doesn't have
the same properties or methods?
On Thu, Nov 14, 2013 at 12:20 PM, Pierre du Plessis
pierre@pcservice.co.zawrote:
Do you have any use cases why this would be useful?
Basically the reason I want it is to make sure the function I just called
returned the object I expected. Kind of like how we can specify what object
we're expecting for parameters like this:
function foo (User $user) {
// do stuff here
}
I want to be able to make sure the object I have is the right type. In this
case, I wouldn't want it to actually change the object type but instead
through an exception I could catch. (obviously, there are ways to do that
now by writing my own functions and throwing my own exceptions). The
company I'm working for right now uses Zend Framework 2 and in Zend
Framework 2 they have factories which could return just about anything and
I want to make sure I got the right object.
Another minor use case would be that the IDE would be able to do
function/parameter hints. (I do know some IDEs will let you do /* @var
$foo User */ )
Another situation we ran across is we're using Propel and we would pull
database records that Propel turns into User objects. If some of those
users are admins then we would want them to be AdminUser objects. Obviously
you could write work arounds like :
$admin = AdminUser::createFromUser($user);
or
$admin = new AdminUser($user);
but if AdminUser extends User I could see how type casting could be
appropriate.
Also what would happen if you cast objects to invalid types?
E.G if you convert a person entity to a product entity, which doesn't have
the same properties or methods?
I imagine throwing an exception would be appropriate
Thanks!
Chris London
On Thu, Nov 14, 2013 at 12:20 PM, Pierre du Plessis
pierre@pcservice.co.zawrote:Do you have any use cases why this would be useful?
Basically the reason I want it is to make sure the function I just called
returned the object I expected. Kind of like how we can specify what object
we're expecting for parameters like this:function foo (User $user) {
// do stuff here
}I want to be able to make sure the object I have is the right type. In this
case, I wouldn't want it to actually change the object type but instead
through an exception I could catch. (obviously, there are ways to do that
now by writing my own functions and throwing my own exceptions). The
company I'm working for right now uses Zend Framework 2 and in Zend
Framework 2 they have factories which could return just about anything and
I want to make sure I got the right object.
I’m confused - on one hand, you’re referring to the already existing type hinting of parameters. On the other, you’re making the case for instanceof.
if ($someZendObject instanceof What\I\Expect)
This really isn’t a use case for object type casting.
Another minor use case would be that the IDE would be able to do
function/parameter hints. (I do know some IDEs will let you do /* @var
$foo User */ )
If you’re using a relatively modern IDE and using parameter type hinting, this already exists.
Another situation we ran across is we're using Propel and we would pull
database records that Propel turns into User objects. If some of those
users are admins then we would want them to be AdminUser objects. Obviously
you could write work arounds like :$admin = AdminUser::createFromUser($user);
or
$admin = new AdminUser($user);
but if AdminUser extends User I could see how type casting could be
appropriate.
There are two types of object type casts: upcast and downcast. Upcast is when you have a class that refers to an object that inherits from a root class. Downcast is casting an object from the root towards a child or subclass. What you’re referring to is downcasting. (User -> AdminUser). This is the only valid use case provided thus far, but even then, there are other methodological approaches that can be taken to mitigate this scenario.
Also what would happen if you cast objects to invalid types?
E.G if you convert a person entity to a product entity, which doesn't have
the same properties or methods?
I imagine throwing an exception would be appropriate
Thanks!
Chris London
On Thu, Nov 14, 2013 at 12:20 PM, Pierre du Plessis
pierre@pcservice.co.zawrote:Do you have any use cases why this would be useful?
Basically the reason I want it is to make sure the function I just called
returned the object I expected. Kind of like how we can specify what
object
we're expecting for parameters like this:function foo (User $user) {
// do stuff here
}I want to be able to make sure the object I have is the right type. In
this
case, I wouldn't want it to actually change the object type but instead
through an exception I could catch. (obviously, there are ways to do that
now by writing my own functions and throwing my own exceptions). The
company I'm working for right now uses Zend Framework 2 and in Zend
Framework 2 they have factories which could return just about anything
and
I want to make sure I got the right object.I’m confused - on one hand, you’re referring to the already existing type
hinting of parameters. On the other, you’re making the case for instanceof.
My reference to already existing functionality was to try and prove there's
a need for type casting.
if ($someZendObject instanceof What\I\Expect)
This really isn’t a use case for object type casting.
If we implemented type casting we could reduce the following:
$factory = $this->getServiceManger()->get('myObject');
if ($factory instance of \Parent\Of\What\I\Expect)
{
$factory = \What\I\Expect::convertFromParentOfWhatIExpect($factory);
}
elseif (!$factory instance of \What\I\Expect)
{
throw new Exception('Invalid factory return');
}
to
$factory = (\What\I\Expect) $this->getServiceManger()->get('myObject');
I think the one line is cleaner than having an "if instanceof" after every
factory request.
Another minor use case would be that the IDE would be able to do
function/parameter hints. (I do know some IDEs will let you do /* @var
$foo User */ )If you’re using a relatively modern IDE and using parameter type hinting,
this already exists.
Yes, but only if the function returns a single type.
Another situation we ran across is we're using Propel and we would pull
database records that Propel turns into User objects. If some of those
users are admins then we would want them to be AdminUser objects.
Obviously
you could write work arounds like :$admin = AdminUser::createFromUser($user);
or
$admin = new AdminUser($user);
but if AdminUser extends User I could see how type casting could be
appropriate.There are two types of object type casts: upcast and downcast. Upcast is
when you have a class that refers to an object that inherits from a root
class. Downcast is casting an object from the root towards a child or
subclass. What you’re referring to is downcasting. (User -> AdminUser).
This is the only valid use case provided thus far, but even then, there
are other methodological approaches that can be taken to mitigate this
scenario.
I actually think it should also work for upcasting if you follow the Liskov
Substitution Principle. To me, since PHP already has implemented type
casting to (int), (string), (bool), etc. the value in type casting is
already there. I just want to take it to the next level.
Also what would happen if you cast objects to invalid types?
E.G if you convert a person entity to a product entity, which doesn't
havethe same properties or methods?
I imagine throwing an exception would be appropriate
Thanks!
Chris London
Chris London wrote (on 15/11/2013):
To me, since PHP already has implemented type
casting to (int), (string), (bool), etc. the value in type casting is
already there. I just want to take it to the next level.
Actually, PHP has so far avoided user-defined casts to anything other
than string. Extensions can implement objects with magic cast semantics
for other types, but only __toString() can be implemented in user-space,
and proposals for more general casting mechanisms seem to have met with
resistance.
Whether or not that's a discussion people are willing to re-visit, I
have no idea.
Regards,
Rowan Collins
[IMSoP]
Hi Chris
-----Original Message-----
From: Chris London [mailto:me@chrislondon.co]
Sent: Thursday, November 14, 2013 7:40 PM
To: internals@lists.php.net
Subject: [PHP-DEV] Proposal: Type Casting User ClassesHey everyone,
I want to get a feel for something I would like to implement into PHP
core.
I would like to be able to cast objects to my own class types. Similar
to
already available PHP syntax:$foo = (string) $bar;
The syntax would look like this:
$foo = (\My\FooObject) $bar;
It would also support:
$foo = ( (\My\FooObject) $bar )->myFooObjectFunction();
I'm still trying to decide what would happen internally and I would like
some
feedback. I'm thinking of 3 different things it could do:
Directly change the class type, functionally equivalent to:
function castType($object, $type) {
return unserialize(preg_replace("/^O:[0-9]+:"[^"]+":/i",
"O:".strlen($type).":"$type":", serialize($object)));
}
[Robert Stoll]
Seems a bad idea to me. Would be slow and is not really a cast but a
conversion.
- Throw a fatal error if the object doesn't match the type trying to be
cast.
[Robert Stoll]
That's what I would expect from a runtime type checking mechanism
- Add a new static magic method called __cast() that would allow the
developer to decide what happens. Like this:namespace \My;
class FooObject {
public static function __cast($foo) {
// Do stuff here
return new FooObject;
}
}
[Robert Stoll]
This is interesting but goes beyond casts and is a conversion mechanism
again. But would be truly helpful for utility classes like Email etc. (there
was already another discussion about this feature as far as I remember)
I'm willing to implement this myself if everybody else likes this idea.
Thanks!Chris London
You might be interested in my project Type-Safe PHP (TSPHP):
http://tsphp.tutteli.ch
It is far from a real release candidate but that's mostly because I am the
only developer contributing to this project so far (and unfortunately I do
not really have time lately).
Other contributors are welcome ;-)
TSPHP would substitute object casts for you (without the performance penalty
of an additional function call and the use of is_a) but you could emulate it
with a function this way:
function cast($object, $type){
if($object == null || \is_a($object,$type)){
return $object;
}else{
trigger_error('cast error, $object was not of type '.$type);
}
}
I have chosen trigger_error to be consistent with (int), (string) etc. which
trigger an error as well if something goes wrong. Replace it with a throw if
you like.
And use it like this - a one liner as well ;-)
class A{}
class B extends A{}
$a = new B();
$a = cast($a, "A");
Of course, $a will not change its type but is guaranteed to be of type A.
There was a discussion about down casting in another email. That's not
possible with this function since it is a cast and not a conversion. Btw the
Liskov Substitution Principle tells that you should be able to replace your
class with your sub-class without breaking things but not that a class
magically can do the same thing as a sub-class. But you were probably
thinking about a conversion/mapping and fair enough, that might be possible
but there are a lot of cases where this is not as straight forward as you
may think.
Cheers,
Robert
As a thought. Would it not be possible to, instead of implementing a new
__cast method, make use of the existing __clone functionality? Effectively
aren't you trying to clone one object into another just of another type?
While I can't think of many use cases off the top of my head, I certainly
see the benefit of this proposal and it doesn't seem to have any BC that I
can see.
(First post so please excuse me if I'm missing things / making a daft
point.)
Cheers.
Jonny.
Hey everyone,
I want to get a feel for something I would like to implement into PHP core.
I would like to be able to cast objects to my own class types. Similar to
already available PHP syntax:$foo = (string) $bar;
The syntax would look like this:
$foo = (\My\FooObject) $bar;
It would also support:
$foo = ( (\My\FooObject) $bar )->myFooObjectFunction();
I'm still trying to decide what would happen internally and I would like
some feedback. I'm thinking of 3 different things it could do:
Directly change the class type, functionally equivalent to:
function castType($object, $type) {
return unserialize(preg_replace("/^O:[0-9]+:"[^"]+":/i",
"O:".strlen($type).":"$type":", serialize($object)));
}Throw a fatal error if the object doesn't match the type trying to be
cast.Add a new static magic method called __cast() that would allow the
developer to decide what happens. Like this:namespace \My;
class FooObject {
public static function __cast($foo) {
// Do stuff here
return new FooObject;
}
}I'm willing to implement this myself if everybody else likes this idea.
Thanks!Chris London
Hey everyone,
I want to get a feel for something I would like to implement into PHP core.
I would like to be able to cast objects to my own class types. Similar to
already available PHP syntax:$foo = (string) $bar;
I've thought about this feature before in the context of PHP because I
do think it would be useful, but I'm not sure if there's really a way
to implement it that is any better than userland solutions. Your
proposed __cast() call means that every object needs to know about
every object which breaks down when multiple projects are involved.
(Sure, it solves some cases nicely.)
And I wouldn't be a fan of implicit conversions like:
function foo (Foo $foo) { }
foo(new Bar());
Assuming Bar does not descend from Foo, I wouldn't want a cast to Foo
to be implicitly performed even if Bar had an appropriate "cast"
method.
So ultimately, I don't really see how a PHP solution is any better
than just using home made explicit casts:
$bar = cast_foo_to_bar($foo);
or
$bar = $foo->castToBar();
So I think the idea has merit, but I don't know that there's a good
way to implement it.
--
Matthew Leverton