PHP 5 - 序列化对象并存储它们的关系

I am writing a fairly complex PHP applications where a single user action can trigger changes in many other sub-systems, and I'm contemplating using an observer pattern. However, I am wondering if I have to re-create all the objects involved.

Is it possible to while serializing objects to store their relationships? For example

$equipmentHandler = new EquipmentHandler();
$character = new Character();
$character->subscribeOnEquipmentChanged($equipmentHandler);

$_SESSION['character'] = serialize($character);
$_SESSION['subscriber'] = serialize($equipmentHandler);

Will the relationship be preserved after unserializing? Or do I have do lump them all into one object?

$cache['character'] = $character;
$cache['subscriber'] = $equipmentHandler;
$_SESSION['cache'] = serialize($cache);

Any advice would be appreciated.

(PS. The character data requires many DB requests to create and I am thinking of storing it by doing a write to cache and DB, but only read from cache policy, so it will be serialized anyway)

A relation will be kept, but it will be different than you expect. When you serialize two instances of Character that both refer to the same EquipmentHandler, you're going to get two separate instances of this EquipmentHandler, instead of the single one you expected. As this example illustrates:

<?php

echo "BEFORE SERIALIZE:
";
class A { }
class B { }

$a = new A;
$b = new B;
$a -> b = $b;

$a2 = new A;
$a2 -> b = $b;

var_dump($a->b);
var_dump($a2->b);

echo "AFTER SERIALIZE:
";
$a3 = unserialize(serialize($a));
$a4 = unserialize(serialize($a2));

var_dump($a3->b);
var_dump($a4->b);

The output of this is:

BEFORE SERIALIZE:
object(B)#2 (0) {
}
object(B)#2 (0) {
}
AFTER SERIALIZE:
object(B)#5 (0) {
}
object(B)#7 (0) {
}

Look for the number after the pound. This refers to the object ID within PHP. Before serializing both $a->b and $a2->b refer to an object with object ID #2: the same instance. But after the serialization they refer to object IDs #5 and #7: different instances.

This may, or may not, be a problem for you.

To restore the connection to one single B object, you're going to have to get a little tricky. You could use the __sleep() handler in A to flatten the actual reference to an INSTANCE of B to just a mentioning of B: "I had a reference to B". Then implement the __wakeup() handler using that mentioning of a B instance in A to acquire a single instance of a new B object.

BTW. The PHP session extension already does serializing automatically, no need for you to pre-serialize it yourself :)

According to the manual of the serialize function:

The value to be serialized. serialize() handles all types, except the resource-type. You can even serialize() arrays that contain references to itself. Circular references inside the array/object you are serializing will also be stored. Any other reference will be lost.

When serializing objects, PHP will attempt to call the member function __sleep prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup member function is called.

So I guess it is not possible unless you do something smart in the _sleep and _wakeup

Your actually have the solution in your question! More complex cases might need to make use of __sleep and __wakeup ... but given the information you provided, all you have to do is -- as you suggest -- "lump them all into one object".

Explanation

In an answer to a similar question, I said:

Serialization will maintain "relative" references. (Technically, there is no such thing as a relative reference in PHP, but its a good way to conceptualize it.)

If you collect your referenced and referencing variables in an array, serializing the array will save the reference relationship. It won't maintain the original reference, but will automatically recreate it in the context of the new array returned by unserialize. ... It works the same way for internal references in objects.

Example

// example objects
class A {}
class B {}
$a    = new A();
$b    = new B();
$b->a = $a;
// collect referenced and referencing objects in array
$cache = array( 'a' => $a, 'b' => $b );
// flatten and recreate cache (represents data stored & retrieved from db)
$cached = unserialize( serialize( $cache ) );
// overwrite local variables from cache
extract( $cached, EXTR_OVERWRITE );

Then, if you do var_dump( $a ); var_dump( $b->a );, notice in the output below how the object IDs for $a and for $b->a are both '3', indicating they both refer to the same instance of A.

object(A)#3 (0) {
}
object(A)#3 (0) {
}