A dark corner of PHP: class casting

by Arnold Daniels on 02/16/2008

Sometimes you want things that simply look impossible in PHP. A few years ago I was using the DB library from PEAR. I wanted to add some functionality to the DB_Result class, however since the result object is created in the DB class and the class used for that isn’t configurable, I was kind of stuck there.

I solved this problem by writing a class extending the DB_Result class. The class had which used reflection to pull all the info out of the DB_Result class and created a new DB_Result_Ext object. In the end I decided that I didn’t like DB class at all (nor MDB2 for that mather), so I threw away this dirty code, however I always felt that there had to be a better way in solving these kind of issues.

The new solution for this problem I’m about to show, is really a dark corner of PHP and perhaps even dirtier than the other dirty solution. However the method can get you out of a jam when this situation arises.

PHP has a function serialize, which can create a hash from any type of variable, scalars, array, but objects as well. Using the unserialize function, PHP can recreate the variable from the serialized hashed. If we look at how an object is serialized, we see only the properties and the class name are stored. Serializing an object of the ‘mother’ class, with a protected variables ‘$myvar’, would give the following hash:

O:6:"mother":1:{s:8:"�*�myvar";i:5;}

We see an ‘O’ for object, the string length of the class name, the quoted class name and the properties. What would happen if we would change the class name and unserialize?

Let’s have a look at the following example:

We have a class ‘mother’ and a class ‘grandmother’ extending ‘mother’. We create a ‘mother’ object and now we want to cast it to a ‘grandmother’. In the casttoclass() function, we serialize the object, use a preg_replace to change the class name and than unserialize the hash. This creates a ‘grandmother’ object, with a property ‘$myvar’ set to 5, however the ‘$bval’ property is not set. To solve this we can use the magic method __wakeup(). After the conversion, we can use $z normally as any other ‘grandmother’.

Download the code

Arnold Daniels

I've spend a big part of my life behind a computer, learning about databases (MySQL), programming (PHP) and system administration (Linux). Currently I playing with HTML5, jquery and node.js.

More Posts

Follow Me:
TwitterLinkedIn

There are 28 comments in this article:

  1. 16 February 2008Norbert says:

    Clever.

    ReplyReply
  2. 16 February 2008Paul says:

    Wow, this is so dirty ! But quite fun :}

    Why not extending the class that creates the DB_result object so that it can create a DB_result_ext object instead ?

    ReplyReply
  3. 16 February 2008elias says:

    There is another way to solve this cleaner. To retrieve hidden
    object values you can use (object) cast, you only have to clean
    up the property names (they’re suffixed with ‘:protected’ etc.).
    To fill an object with this values use __set_state. (An var_export
    on an class instance is self explaining)

    ReplyReply
  4. 17 February 2008Bruce Weirdan says:

    To elias: care to share your code? The (object) cast didn’t work for me on php 5.2.3 (it produced the object of that same class, so no properties were exposed), though (array) cast + ["" . $className . "" . $propName] index worked for inspection purposes. Using __set_state works for setting private properties if you can define it in the parent class, but it can’t create objects of classes inherited from it (because the array passed to it does not contains the class name it was exported from).

    ReplyReply
  5. 17 February 2008Arnold Daniels says:

    Paul: Often you find yourself copying the code the most important methods of a class to the child. Also with DB you would need to extends a bunch of classes, copy/pasting lots of code, because of the whole factory and driver structure.

    Elias: As Bruce already requested, a code example would be greatly appreciated.

    ReplyReply
  6. 17 February 2008elias says:

    Well, i really thought that would be a no brainer but it revels some obscurities and at last a bug with __set_state and inherited private properties. i have to check it against the current snapshot but here is the code to reproduce to get an idea of it:

    ////////////////////////
    class A {
    	private $foo = 'foo';
    	public static function __set_state($state) {
    		$a = new A();
    		$a->foo = 'bar';
    		return $a;
    	}
    }
    class B extends A {
    	public static function __set_state($state) {
    		$b = new B();
    		$b->foo = 'bar';
    		return $b;
    	}
    }
    $a = A::__set_state(array());
    $b = B::__set_state(array());
    var_dump($a, $b);
    
    /*
    object(A)#1 (1) {
      ["foo:private"]=>
      string(3) "bar"
    }
    object(B)#2 (2) {
      ["foo:private"]=>
      string(3) "foo"
      ["foo"]=>
      string(3) "bar"
    }
    */
    ////////////////////////////////
    

    apart from the bug my idea generally works, but its much more code involved and you have to implement __set_state at last in every base class and have to use custom implementations to handle different constructor values.

    ////////////////////////////////
    class Mother {
    	public $value = 'invaluable';
    	protected $name = 'human';
    	private $type = 'mother';
    	
    	public static function __set_state($state) {
    		$class = __CLASS__;
    		$o = new $class();
    		foreach ($state as $k => $v) {
    			$o->$k = $v;
    		}
    		return $o;
    	}
    
    }
    
    class MotherEarth extends Mother {
    	private $yap = 'dldl';
    	
    	public static function __set_state($state) {
    		$class = __CLASS__;
    		$o = new $class();
    		foreach ($state as $k => $v) {
    			$o->$k = $v;
    		}
    		return $o;
    	}
    
    }
    
    function cleanKeys($prefixes, $data){
    	$ret = array();
    	foreach ($data as $k => $v) {
    		$k = preg_replace('#\W#', '', $k);
    		foreach ($prefixes as $prefix) {
    			if (strpos($k, $prefix) === 0) {
    				$k = substr($k , strlen($prefix));
    				continue;
    			}
    		}
    		$ret[$k] = $v;
    	}
    	return $ret;
    }
    
    function cast($object, $toClass){
    	$prefixes = array($toClass);
    	$class = $toClass;
    	while (true) {
    		$ref = new ReflectionClass($class);
    		$parent = $ref->getParentClass();
    		if (!$parent) {
    			break;
    		}
    		$class = $parent->getName();
    		$prefixes[] = $class;
    	}
    	$objectValues = cleanKeys($prefixes, (array) $object);
    	// testing
    	if (true) {
    		foreach ($objectValues as $k => $v) {
    			$objectValues[$k] = 'passed by __set_state';
    		}
    	}
    	return call_user_func(array($toClass, '__set_state'), $objectValues);
    }
    
    $mom = new Mother();
    $mom2 = cast($mom, 'MotherEarth');
    var_dump($mom, $mom2);
    /*
    object(Mother)#1 (3) {
      ["value"]=>
      string(10) "invaluable"
      ["name:protected"]=>
      string(5) "human"
      ["type:private"]=>
      string(6) "mother"
    }
    object(MotherEarth)#3 (5) {
      ["yap:private"]=>
      string(4) "dldl"
      ["value"]=>
      string(21) "passed by __set_state"
      ["name:protected"]=>
      string(21) "passed by __set_state"
      ["type:private"]=>
      string(6) "mother"
      ["type"]=>
      string(21) "passed by __set_state"
    }
    */
    ////////////////////////////////
    
    ReplyReply
  7. 17 February 2008elias says:

    http://bugs.php.net/44143

    ReplyReply
  8. 18 February 2008Arnold Daniels says:

    This looks like the other solution I was talking about earlier. Using reflection, or in your case reflection + casting to an array, to get all the property values and using that to create a new object. I’d prefer to use ReflectionObject->getProperties(), but the idea is the same.

    You do need to remember that normally you can’t control the code in the Mother class, otherwise you don’t to do this, so accessing and setting the private properties is a problem with this solution. You can get around this by redeclaring the properties in the child (MotherEarth) class as protected. (Which is basically also the solution, for the problem described in your bug report.)

    As you can see, and stated yourself, this is quite tedious and a lot of code has to be executed in order to make the cast. The other solution, though dirty, is quite simple and compact.

    In any case both aren’t really nice, but will get the job done.

    ReplyReply
  9. 18 February 2008elias says:

    I’m fine with the solutions, because i won’t use this anyway :)

    The bug report is marked bogus. After re-thinking i see that the behaviour is intended by the class system and is not related to __set_state.

    So, full access (read/write) with reflection would be the cleanest.

    ReplyReply
  10. 18 February 2008leo says:

    If I understand you wright, you try to downcast a class?! Ever heard of Liskov substitution principle (http://en.wikipedia.org/wiki/Liskov_substitution_principle)? What you try is not provided in object oriented programing. And as far as I know it is not supported in any object oriented language.

    (It might probably better to provide a batch to make the fields that you want to use in the subclass to protected)

    ReplyReply
  11. 16 March 2008Simon says:

    Downcasting a class is never a good idea. You should listen to lea and check out the Liskov substitution principle.

    ReplyReply
  12. 2 April 2008PHP Encoder says:

    Why not? If it works it can be used :)

    ReplyReply
  13. 5 April 2009Bruno Araujo says:

    OMFG! YOU, YOU… YOU ARE A MONSTA! lol

    Man, whata dirty way… but… jawsome!

    kisses

    ReplyReply
  14. 13 April 2009Erwin Haantjes says:

    This solution CAN be a problem when you using destructors in a class, for example database objects that closes the connection in the destructor.

    I also wonder what a fast method is if you want to access one specific function in one of the descendant classes. ( i know the keyword parent but in this case it is useless )

    For example:
    eval( “$”.”result = $sParentClassName::”.$sFuncName.”();” );
    return $result;

    or:
    return casttoclass($sParentClassName, $this)->$sFuncName();

    Notice: in such situation it is impossible to use call_user_func() or call_user_func_array() because of the scope, it must be at least an static function and you cannot access class properties. When you use $eval() inside a class function is possible to do.

    But, what is a faster method or fastest method? Or does anybody know another solution to this?

    ReplyReply
  15. 30 May 2009Samael S. says:

    @leo and Simon

    Here’s why it’s usefull :

    class member
    {
      public static function get ($id)
      {
        return casttoclass('member', 
          db_query ("SELECT * FROM members WHERE id=$id LIMIT 1")
        );
      }
    
      public function getFullName ()
      {
        return $this->firstName.' '.$this->lastName;
      }
    }
    

    If you know some easiest way to do such a thing, tell me !

    ReplyReply
  16. 30 September 2009Yury says:

    Recently I was attempting to cast stdClass resulted from json_decode to my own classes, that’s what I’ve came with:

    http://freebsd.co.il/cast/

    hope it’ll be helpful to someone.

    ReplyReply
  17. 24 October 2009zanlok says:

    Unclean, and beautifully clever!

    ReplyReply
  18. 27 November 2009kentpachi says:

    why not this ?

    function ArrayToObject($array,$class)
    {
     	$obj = new $class;
     	foreach($array as $k=>$v)
     	{
     		$obj->$k = $v;
     	}
     	
     	return $obj;
    }
    
    ReplyReply
  19. 27 November 2009Arnold Daniels says:

    kentpachi: That will only set the public properties, not the private ones.

    However a better alternative is to implement __set_state() as described by elias.

    ReplyReply
  20. 4 December 2009Chris Morrell says:

    Another way to do this would be to use a modified version of the decorator pattern:

    class A
    {
    	protected $property = 'Set in A';
    	
    	public function getProperty()
    	{
    		return $this->property;
    	}
    	
    	public function setProperty($property)
    	{
    		$this->property = $property;
    	}
    }
    
    class B extends A
    {
    	public function __construct(A $class, $options = array())
    	{
    		$this->_class = $class;
    	}
    	
    	public function __set($name, $value)
    	{
    		$this->_class->$name = $value;
    	}
    	
    	public function __get($name)
    	{
    		return $this->_class->$name;
    	}
    	
    	public function __isset($name)
    	{
    		return isset($this->_class->$name);
    	}
    	
    	public function __unset($name)
    	{
    		unset($this->_class->$name);
    	}
    }
    
    $a = new A();
    $b = new B($a);
    
    $b->setProperty('Set via B');
    echo $b->getProperty();
    

    Chris

    ReplyReply
  21. 18 December 2009Aurelian Toma says:

    i have the reverse :))

    // lambda method declaration here
    $methodHereIs = function() {
    ….
    return “someReturn”;
    }

    $fullObjectOnTheFly = (object) array(
    “aProperty” => “someValue”, #propertyOnTheFly
    “aMethod” => $methodHere…. #methodOnTheFly
    ….more

    ReplyReply
  22. 13 January 2010peterchen says:

    Hi Arnold,

    thanks for sharing this. I was struggling with copying my class manually and that’s bullshit. Your version is nice although hacked and works like a charm :)

    BR
    Christoph

    ReplyReply
  23. 10 February 2010Nacho says:

    Thanks for this!

    ReplyReply
  24. 27 March 2010kentpachi says:

    ADaniels > That will only set the public properties, not the private ones.

    However a better alternative is to implement __set_state() as described by elias.

    I think it will set privates too. if they have a setter.

    if not this a design problem not implementation

    if i do

    $user->name = “john”

    if name is public its ok or if it has magic setter (__set) or hard setter (setName($val)) method

    so this will set the properties no matter its visibility

    ReplyReply
  25. 16 March 2012aurelian toma says:

    inside a method:
    unset($this); //does nothing:))

    ReplyReply
  26. 1 June 2013podanenko.com says:

    Hey, I think your site might be having browser compatibility issues.
    When I look at your blog in Ie, it looks fine but when opening in
    Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up!
    Other then that, terrific blog!

    ReplyReply
  27. 13 January 2014Gvanto says:

    Thanks, this is a good article. I was trying to do casting using (WP_Post) $variable. (just to use Netbean’s autocomplete feature) but ran into some issues (it doesn’t work).

    You can’t quite using Boxing with PHP as you can with Java I guess.

    ReplyReply
  28. 15 April 2014mobile games says:

    I read this post completely on the topic of the difference of hottest and earlier technologies, it’s remarkable
    article.

    ReplyReply

Write a comment:


4 − = two