Freezing and Thawing PHP Objects

Sebastian Bergmann » 29 November 2008 » in Articles, New Features » 12 Comments

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.

Defined tags for this entry: , ,

Trackback specific URI for this entry

12 Comments to "Freezing and Thawing PHP Objects"

Display comments as (Linear | Threaded)
  1. Christian Weiske
    30/11/2008 at 13:25 Permalink
    Sorry but this is, like Kris noted, a big bad ugly hack.

    Reply

  2. Sebastian Bergmann
    01/12/2008 at 02:33 Permalink
    A hack is casting an object to an array to access its non-public attributes.

    Please read my comment on Kris' blog.

    Reply

  3. romanb
    15/12/2008 at 16:05 Permalink
    That is a very, very useful addition and I'm glad it's finally possible to do this. I can't quite understand the negative comments about this. Every language i know that has PPP also has a means to get around the regular rules through reflection or other means to enable meta-programming. These are not mutually-exclusive concepts. PPP is a good thing because it makes APIs more robust according to the rule "A good API is easy to use correctly and hard to use incorrectly." I, for one, dont want to miss it, yet i want to be able to circumvent these restrictions through meta-programming which is very useful for many tools.

    Reply

  4. Stan Vassilev
    20/12/2008 at 20:49 Permalink
    Freeze/thaw come very close to perfect universal interface for serialize/unserialize() and var_export(), if they were on the class itself (then we wouldn't even need the Reflection magic).

    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

  5. Sebastian Bergmann
    21/12/2008 at 09:22 Permalink
    The point of my implementation of freeze() and thaw() is that each and every PHP object can be used with them. PHP objects are oblivious to freeze() and thaw() and do not have to implement an interface, for instance.

    Reply

  6. gaj
    01/01/2009 at 11:41 Permalink
    What about if object has a property which includes another object which has private/protected properties?
    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

  7. Sebastian Bergmann
    01/01/2009 at 11:45 Permalink
    Please look at the current state of Object_Freezer over at Launchpad and see that object graphs, includes those that contain cycles, are supported.

    Reply

  8. Koen
    28/02/2009 at 13:17 Permalink
    Would freezing objects, serializing them and storing them in a mysql database a slower alternative to an object storage or would that be real slow? I have objects in mind that are otherwise really expensive to generate of course.

    Reply

  9. Mark
    06/07/2009 at 15:56 Permalink
    This is only the second example of OOP code which is significantly different from PHP4's OOP model. The other is try catch block. All other OOP examples in PHP5 code are pretty much just syntactic sugar, or are rendered useless because of non-catchable fatal errors.

    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

  10. ExSystem
    29/07/2009 at 14:12 Permalink
    but I wonder how to serialize a following object in your way..
    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

  11. Sebastian Bergmann
    29/07/2009 at 17:30 Permalink
    http://github.com/sebastianbergmann/php-object-freezer/commit/d50dfc5c21982e0206c08b9cc74a53f8258de3fa

    Reply

  12. ExSystem
    30/07/2009 at 10:39 Permalink
    actually i am working on how to solve the same problem as u mentioned in this blog...

    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

4 Trackbacks to "Freezing and Thawing PHP Objects"

  1. Die wunderbare Welt von Isotopp 29/11/2008 at 21:09
    Ich erinnere mich daran, daß vor 10 Jahren oder so eine Diskussion lief, bei der bemängelt wurde, daß PHP (damals Version 3) keine "Zugriffskontrolle" bei Objektattributen implementiere. Ich erlaubte mir damals einzuwerfen, daß das ein sinnloses Unte
  2. Mirin blog 02/12/2008 at 15:53
    Na webu Sebastiana Bergmanna, známého p?edev?ím z projektu PHPUnit a v neposlední ?ad? i p?isp?vatele do samotného PHP, vy?el zajímavý p?ísp?vek o novince, která bude v PHP 5.3. Jedná se o metodu setAccessible() ve t?íd? ReflectionProperty, která je sou?ástí Reflection API. Jde o to, ?e tato metoda umo??uje p?ístup k private a protected atribut?m objektu nebo t?í
  3. Sebastian Bergmann 11/01/2009 at 17:50
    The Object_Freezer library for PHP provides the low-level functionality to store ("freeze") and fetch ("thaw") any PHP userland object to and from arbitrary object stores.Apache CouchDB, the distributed, fault-tolerant and schema-free document-oriented da
  4. Sebastian Bergmann 16/02/2009 at 12:09
    The Object_Freezer library that I introduced earlier here and here now supports all three object-relational behavioral patterns listed in Martin Fowler's "Patterns of Enterprise Application Architecture" book.Unit of Work Maintains a list of objects affe

Add Comment


To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Submitted comments will be subject to moderation before being displayed.