- URL: https://www.laruence.com/en/2011/03/18/1916.html
- Please include attribution when republishing.
In the last article, Serialize/Unserialize breaks the Singleton, I left a question at the end. To let everyone think about it, I'll write a separate article giving the answer.
In the previous article, we said that to implement a serialization-friendly Singleton, we used the following definition:
class Singleton {
private static $instance = NULL;
/** do not allow direct constructor calls */
private function __construct() {
}
/** do not allow deep copy */
private function __clone() {
}
public function __wakeup() {
self::$instance = $this;
}
/** cleanup is needed when the singleton switches over */
public function __destruct() {
//cleanup work
....
self::$instance = NULL;
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
But this seemingly-correct code fails to achieve what we want in some situations:
$a = Singleton::getInstance(); $a = unserialize(serialize($a)); var_dump($a === Singleton::getInstance()); //bool(false)
So why?
In my earlier article Variables Separation/Reference in PHP Internals, I once introduced that PHP uses reference counting to reduce memory usage and improve efficiency.
Looking back at this problem, let's analyze this process step by step, based on the associativity of the operators.
When we call unserialize(serialize($a)), before serialize, PHP first tries to call our class instance $a's __sleep method. Since we didn't define it, this step is skipped..
Next, during unserialize, after PHP finishes creating the object, it calls the __wakeup method of the newly created object. In there, we release the reference to the original self::$instance and change it to the new object.
At this point, the original $a is not released, because the symbol name a still holds a reference to $a (an instance of the Singleton class). But at this moment, the object $a points to has its reference count decremented by 1, becoming 1. (It's also worth knowing that at this point the reference count of the object in the Object Store is also decremented by 1, also becoming 1.)
Finally, we assign the newly obtained object to $a. OK, the critical moment arrives. Because we've reassigned $a, $a releases its reference to the zval it previously pointed to, causing that zval's reference count to become zero. So PHP releases that zval, which triggers the call to Singleton's destructor. In that destructor, we release the static instance $instance..
Do you see it now?
Of course, the final version should be written like this:
class Singleton {
private static $instance = NULL;
/** do not allow direct constructor calls */
private function __construct() {
}
/** do not allow deep copy */
private function __clone() {
}
public function __wakeup() {
self::$instance = $this;
}
/** cleanup is needed when the singleton switches over */
public function __destruct() {
//only do cleanup work.
}
public static function getInstance() {
if (NULL === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
Be First to Comment