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:
[snip php-source]code/class_casting/class_casting.php[/snip]

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

  24 Responses to “A dark corner of PHP: class casting”

  1. Clever.

    ReplyReply
  2. 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. 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. 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. 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. 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. 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
  8. 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
  9. 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
  10. Downcasting a class is never a good idea. You should listen to lea and check out the Liskov substitution principle.

    ReplyReply
  11. Why not? If it works it can be used :)

    ReplyReply
  12. OMFG! YOU, YOU… YOU ARE A MONSTA! lol

    Man, whata dirty way… but… jawsome!

    kisses

    ReplyReply
  13. 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
  14. @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
  15. 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
  16. Unclean, and beautifully clever!

    ReplyReply
  17. why not this ?

    function ArrayToObject($array,$class)
    {
     	$obj = new $class;
     	foreach($array as $k=>$v)
     	{
     		$obj->$k = $v;
     	}
    
     	return $obj;
    }
    
    ReplyReply
  18. 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
  19. 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
  20. i have the reverse :) )

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

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

    ReplyReply
  21. 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
  22. Thanks for this!

    ReplyReply
  23. 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

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">

   

Questions? Just ask!

About the author

Hi, I'm Arnold Daniels. How nice that you like to know a bit more about little old me :).

I've spend a big part of my life behind a computer (and not playing games). I've learned a lot about databases, programming and system administration especially on. the LAMP stack (Linux, Apache, MySQL & PHP).

Have a look at what I'm working on now!
© 2012 Jasny · web development Suffusion theme by Sayontan Sinha