Just like we do with __ToString, is there a way to define a method for casting?
$obj = (MyClass) $another_class_obj;
Here's a function to change the class of an object:
/**
* Change the class of an object
*
* @param object $obj
* @param string $class_type
* @author toma at smartsemantics dot com
* @see http://www.php.net/manual/en/language.types.type-juggling.php#50791
*/
function changeClass(&$obj,$new_class)
{
if(class_exists($class_type,true))
{
$obj = unserialize(preg_replace("/^O:[0-9]+:\"[^\"]+\":/i",
"O:".strlen($class_type).":\"".$new_class."\":", serialize($obj)));
}
}
In case it's not clear, this is not my function, it was taken from a post by "toma at smartsemantics dot com" on http://www.php.net/manual/en/language.types.type-juggling.php#50791
I think you mean Type-Hinting.
As of PHP 7.2, you can type-hint arguments in functions:
function something(Some_Object $argument) {...} # Type-hinting object on function arguments works on PHP 7.2+
But you can't type-hint it like this:
(Some_Object) $variable = get_some_object($id); # This does not work, even in PHP 7.2
The alternative for type-hinting objects while it isn't implemented officialy in PHP, is:
$variable = get_some_object($id); # We expect Some_Object to return
is_a($argument, 'Some_Object') || die('get_some_object() function didn't return Some_Object');
If casting for type hinting is all you're after, this works.
if( is_object($dum_class_u_want) && $dum_class_u_want instanceof ClassYouWant )
{
// type hints working now
$dum_class_u_want->is_smart_now();
}
Yep.
Even on PHP 7.2 if you try simple type casting, like so
class One
{
protected $one = 'one';
}
class Two extends One
{
public function funcOne()
{
echo $this->one;
}
}
$foo = new One();
$foo = (Two) $foo;
$foo->funcOne();
You'll get error like this
PHP Parse error: syntax error, unexpected '$foo' (T_VARIABLE) in xxx.php on line xxx
So you basically cannot do that, but think again, maybe you wanted only a new function on top of other public functionality of the class?
You can do that using Wrapper
class One
{
protected $one;
public function __construct(string $one)
{
$this->one = $one;
}
public function pub(string $par){
echo (__CLASS__ . ' - ' . __FUNCTION__ . ' - ' . $this->one . '-' .$par);
}
}
class Wrapper
{
private $obj;
public function __construct(One $obj)
{
$this->obj = $obj;
}
public function newFunction()
{
echo (__CLASS__ . ' - ' . __FUNCTION__);
}
public function __call($name, $arguments)
{
return call_user_func_array([$this->obj, $name], $arguments);
}
}
$foo = new One('one');
$foo->pub('par1');
$foo = new Wrapper($foo);
$foo->pub('par2');
$foo->newFunction();
One - pub - one-par1
One - pub - one-par2
Wrapper - newFunction
What if you want to get a protected property out?
You can do that too
class One
{
protected $one;
public function __construct(string $one)
{
$this->one = $one;
}
}
$foo = new One('one');
$tmp = (new class ($foo) extends One {
protected $obj;
public function __construct(One $obj)
{
$this->obj = $obj;
parent::__construct('two');
}
public function getProtectedOut()
{
return $this->obj->one;
}
} )->getProtectedOut();
echo ($tmp);
You'll get
one
And you can get_protected_in the same way
Although there's no need to type cast in PHP, you might come across a situation where you would like to convert a parent object into a child object.
//Example of a sub class
class YourObject extends MyObject {
public function __construct(MyObject $object) {
foreach($object as $property => $value) {
$this->$property = $value;
}
}
}
$my_object = new MyObject();
$your_object = new YourObject($my_object);
So all you do is pass the parent object down to the child object's constructor, and let the constructor copy over the properties. You can even filter / change them as needed.
//Class to return standard objects
class Factory {
public static function getObject() {
$object = new MyObject();
return $object;
}
}
//Class to return different object depending on the type property
class SubFactory extends Factory {
public static function getObject() {
$object = parent::getObject();
switch($object->type) {
case 'yours':
$object = new YourObject($object);
break;
case 'ours':
$object = new OurObject($object);
break;
}
return $object;
}
}
//Example of a sub class
class YourObject extends MyObject {
public function __construct(MyObject $object) {
foreach($object as $property => $value) {
$this->$property = $value;
}
}
}
It's not type casting, but it does what you need.
I reworked the function Josh posted (which will error because of the undefined $new_class variable). Here's what I got:
function changeClass(&$obj, $newClass)
{ $obj = unserialize(preg_replace // change object into type $new_class
( "/^O:[0-9]+:\"[^\"]+\":/i",
"O:".strlen($newClass).":\"".$newClass."\":",
serialize($obj)
));
}
function classCast_callMethod(&$obj, $newClass, $methodName, $methodArgs=array())
{ $oldClass = get_class($obj);
changeClass($obj, $newClass);
// get result of method call
$result = call_user_func_array(array($obj, $methodName), $methodArgs);
changeClass(&$obj, $oldClass); // change back
return $result;
}
It works just like you'd expect a class cast to work. You could build something similar for accessing class members - but I don't think I would ever need that, so i'll leave it to someone else.
Boo to all the jerks that say "php doesn't cast" or "you don't need to cast in php". Bullhockey. Casting is an important part of object oriented life, and I wish I could find a better way to do it than ugly serialization hacks.
So thank you Josh!