Freezing and Thawing PHP Objects

One of the many new features that have been added for PHP 5.3 is the setAccessible() method of the ReflectionProperty class that is part of PHP's Reflection API. This method makes protected and private attributes (unfortunately, the class is called ReflectionProperty instead of ReflectionAttribute) of a class or object accessible for the ReflectionProperty::getValue() and ReflectionProperty::setValue() methods, thus making protected and private attributes "open" for full read and write access from the outside.

Among other use cases, this addition to PHP's meta programming capabilities makes the customized serialization of objects possible as illustrated by the following proof-of-concept implementation:

<?php
class Object_Freezer
{
    public static function freeze($object)
    {
        $state     = array();
        $reflector = new ReflectionObject($object);
 
        foreach ($reflector->getProperties() as $attribute) {
            $attribute->setAccessible(TRUE);
            $state[$attribute->getName()] =
            $attribute->getValue($object);
        }
 
        return array(
          'className' => get_class($object), 'state' => $state
        );
    }
 
    public static function thaw(array $frozenObject)
    {
        if (!class_exists($frozenObject['className'])) {
            throw new RuntimeException(
              sprintf(
                'Class "%s" could not be found.',
                $frozenObject['className']
              )
            );
        }
 
        // Use a "trick" to create an object of the class
        // without calling its constructor. After all, we
        // are not creating a new object but are merely
        // thawing a previously created and currently
        // frozen one.
        $object = unserialize(
          sprintf(
            'O:%d:"%s":0:{}',
            strlen($frozenObject['className']),
            $frozenObject['className']
          )
        );
 
        $reflector = new ReflectionObject($object);
 
        foreach ($frozenObject['state'] as $name => $value) {
            $attribute = $reflector->getProperty($name);
            $attribute->setAccessible(TRUE);
            $attribute->setValue($object, $value);
        }
 
        return $object;
    }
}
?>

The following snippet of code uses the Object_Freezer class (see above) to "freeze" and "thaw" an object:

<?php
require 'Object/Freezer.php';
 
class Foo
{
    public $a;
    protected $b;
    private $c;
 
    public function __construct($a, $b, $c)
    {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }
}
 
$object = new Foo(1, 2, 3);
var_dump($object);
 
$frozenObject = Object_Freezer::freeze($object);
var_dump($frozenObject);
 
$object = Object_Freezer::thaw($frozenObject);
var_dump($object);
?>

Below is the output produced by the script above:

object(Foo)#1 (3) {
  ["a"]=>
  int(1)
  ["b":protected]=>
  int(2)
  ["c":"Foo":private]=>
  int(3)
}
array(2) {
  ["className"]=>
  string(3) "Foo"
  ["state"]=>
  array(3) {
    ["a"]=>
    int(1)
    ["b"]=>
    int(2)
    ["c"]=>
    int(3)
  }
}
object(Foo)#5 (3) {
  ["a"]=>
  int(1)
  ["b":protected]=>
  int(2)
  ["c":"Foo":private]=>
  int(3)
}

The code for the Object_Freezer class (see above) was written by Stefan Priebsch and myself somewhere over the Atlantic ocean during our flight from Frankfurt to Atlanta for the php|works conference earlier this month. However, only as of today (patch) does it actually work.

Update: The code for Object_Freezer is now available on GitHub.