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.
30/11/2008 at 13:25 Permalink
Reply
01/12/2008 at 02:33 Permalink
Please read my comment on Kris' blog.
Reply
15/12/2008 at 16:05 Permalink
Reply
20/12/2008 at 20:49 Permalink
PHP has currently a mixed bowl of serialization/unserialization methods in the form of __sleep, __wakeup and __set_state (where is __get_state? nowhere...) which are awkward or incomplete to use.
I really wish we fix that, it's not hard really:
$array = SomeClass::freeze($someClassInstance);
$someClassInstance = SomeClass::thaw($array);
Those two method solve a bunch of issues with __sleep/wakeup, and also can work for all both var_export and serialize/unserialize.
We can even use them to create custom serializers:
xml_(un)serialize();
json_(un)serialize();
Please drop me a line on my email (you know where :) ) if you will support me to push this for a future version of PHP.
(OT: I think your Preview button is broken :) )
Reply
21/12/2008 at 09:22 Permalink
Reply
01/01/2009 at 11:41 Permalink
I guess this should be made recursive.
Another way of getting private/protected stuff out of an object into array:
$exported = var_export($var, true);
$exported = preg_replace('/([\S]+)::__set_state\(/', 'array(\'OBJECT::$1\' => ', $exported);
eval('$exported = ' . $exported . ';');
var_dump($exported);
Reply
01/01/2009 at 11:45 Permalink
Reply
28/02/2009 at 13:17 Permalink
Reply
06/07/2009 at 15:56 Permalink
This utility class should be made available to all classes via an interface, which fills in the __sleep __wakeup magic methods with freeze/thaw.
With PHP 5.3 we have now caught up with 1999 Java.
Reply
29/07/2009 at 14:12 Permalink
class TObject {
private $FData1;
public function Test() { echo $this->FData1."\n"; }
public function Set($Val) { $this->FData1 = $Val; }
}
class TChild extends TObject {
private $FData2;
private $FData3;
public function Set($Val) {
$this->FData2 = $Val;
$this->FData3 = $Val;
parent::Set($Val);
}
}
$mObj = new TChild();
$mObj->Set(100);
if you serialize $mObj in your way, you will lost the private property $FData1 defined in the parent class(TObject, in this case).
Since I can create a ReflectionProperty to reflect the $FData1 with the code:
$p=new ReflectionProperty('TObject', 'FData1');
but I have no way to get or set the value stored inside $mObj.
If you do something like $p->getValue($mObj), PHP will call the magic function __get(), and returns what it gets, or simply returns NULL if no magic __get() defined.
The same thing will happen on calling $p->setValue($mObj, 100) .
===
sorry for my poor english, and the code is coded manually.
Reply
29/07/2009 at 17:30 Permalink
Reply
30/07/2009 at 10:39 Permalink
and my solution is to use the built-in serialize() / unserialize() and Reflection(to record class name, where it is defined and static info, etc.) of PHP5.3..i have tried 5.2 but failed, :( REALLY NO~ WAY~
there's a feature of mine is that you can do custom sleeping before serialization and custom waking up after unserialization.
since the code is relatively long, i will not paste here...
Reply