Use of PHP Magic Methods __sleep and __wakeup
As already described, __sleep()
is called when you serialize()
an object and __wakeup()
after you unserialize()
it.
Serialization is used to persist objects: You will get a representation of an object as a string that can then be stored in $_SESSION
, a database, cookies or anywhere else you desire.
Resource values
However, serialize()
cannot serialize (i.e. transform into a textual representation) values of the resource type. This is why all of these values will go missing after unserialize()
ing it.
Object graph
or members, and the member's members and the ... ad infinitum
Another, perhaps more important point is, that serialize()
will traverse the entire object graph of $obj
if you serialize it. This is great when you need it, but if you only need parts of the object and certain linked objects are "runtime-specific" and shared across a lot of objects but also by other objects, you may not want that behavior.
PHP handles cyclic graphs correctly! Meaning: If (a member of) $a links to $b, and $b links to $a is handled correctly however many levels deep.
Example - session specific (shared) objects
For instance, a $database
object is referenced by $obj->db
, but also by other objects. You will want $obj->db
to be the same objects - after unserialize()
ing - that all the other objects in your next session have, not an isolated instance of the database object.
In this case, you would have __sleep()
method such as this:
/**
/* DB instance will be replaced with the one from the current session once unserialized()
*/
public function __sleep() {
unset($this->db);
}
and then restore it like this:
public function __wakeup() {
$this->db = <acquire this session's db object>
}
Another possibility is, that the object is part of some (global) datastructure where it needs to be registered. You could do this manually of course:
$obj = unserialize($serialized_obj);
Thing::register($obj);
However, if it is part of the objects contract that it needs to be in that registry, it's not a good idea to leave this magical call up to the user of your object. The ideal solution is, if the object cares about its responsibilities, i.e. being registered in Thing
. That's what __wakeup()
allows you to do transparently (i.e. he need no longer worry about that magical dependency) to your client.
Similarly, you could use __sleep()
to "un-register" an object if appropriate. (Objects are not destroyed when they're serialized, but it may make sense in your context.)
Closures
Last but not least, closures do not support serialization either. This means that you will have to re-create all attached closures in __wakeup()
.
These methods are used when calling serialize() and unserialize() on the objects to make sure you have a hook to remove some properties like database connections and set them back when loading. This happens when storing objects in sessions among other things.
Since PHP 7.4 there will be new methods __serialize() and __unserialize() available which should slightly change the usage of __sleep and __wakeup magic methods.
PHP currently provides two mechanisms for custom serialization of objects: The __sleep()/__wakeup() magic methods, as well as the Serializable interface. Unfortunately, both approaches have issues that will be discussed in the following. This RFC proposes to add a new custom serialization mechanism that avoids these problems.
More in PHP RFC manual https://wiki.php.net/rfc/custom_object_serialization.
// Returns array containing all the necessary state of the object.
public function __serialize(): array;
// Restores the object state from the given data array.
public function __unserialize(array $data): void;
The usage is very similar to the Serializable interface. From a practical perspective the main difference is that instead of calling serialize() inside Serializable::serialize(), you directly return the data that should be serialized as an array.
The following example illustrates how __serialize()/__unserialize() are used, and how they compose under inheritance:
class A {
private $prop_a;
public function __serialize(): array {
return ["prop_a" => $this->prop_a];
}
public function __unserialize(array $data) {
$this->prop_a = $data["prop_a"];
}
}
class B extends A {
private $prop_b;
public function __serialize(): array {
return [
"prop_b" => $this->prop_b,
"parent_data" => parent::__serialize(),
];
}
public function __unserialize(array $data) {
parent::__unserialize($data["parent_data"]);
$this->prop_b = $data["prop_b"];
}
}
This resolves the issues with Serializable by leaving the actual serialization and unserialization to the implementation of the serializer. This means that we don't have to share the serialization state anymore, and thus avoid issues related to backreference ordering. It also allows us to delay __unserialize() calls to the end of unserialization.
They are pretty much like hook functions, which we can use according to our needs. I came up with this simple real time example. Now try executing this code in two scenarios:
class demoSleepWakeup {
public $resourceM;
public $arrayM;
public function __construct() {
$this->resourceM = fopen("demo.txt", "w");
$this->arrayM = array(1, 2, 3, 4); // Enter code here
}
public function __sleep() {
return array('arrayM');
}
public function __wakeup() {
$this->resourceM = fopen("demo.txt", "w");
}
}
$obj = new demoSleepWakeup();
$serializedStr = serialize($obj);
var_dump($obj);
var_dump($serializedStr);
var_dump(unserialize($serializedStr));
Scenario 1:
First by commenting __sleep()
and __wakeup()
methods, check the output. You will find the resource missing when you unserialize it.
Scenario 2:
Now try running it uncommenting them, you will figure out that the object dumped in first and last var_dump
would be same.