读过鸟哥的博客,一直以为常规定义单例的方式就很正确,结果看完博客后发现序列化能够破坏单例。
原文地址:laruence http://www.laruence.com

常规定义单例的方式:

class Singleton {  
    private static $instance = NULL;  
    /** 不容许直接调用构造函数 */  
    private function __construct() {  
    }  
    /** 不容许深度复制 */  
    private function __clone() {  
    }  
    public static function getInstance() {  
        if (NULL === self::$instance) {  
             self::$instance = new self();  
        }  
        return self::$instance;  
    }  
} 

Serialize/Unserialize破坏单例

$a = Singleton::getInstance(); 
$b = unserialize(serialize($a)); 
var_dump($a === $b); //bool(false)

修改1 (阻止Serialize/Unserialize)

class Singleton {
    private static $instance = NULL;
    /** 不容许直接调用构造函数 */
    private function __construct() {
    }
    /** 不容许深度复制 */
    private function __clone() {
    }
    /** 不容许serialize */
    private function __sleep() {
    }
    /** 不容许unserialize */
    private function __wakeup() {
    }
    public static function getInstance() {
        if (NULL === self::$instance) {
             self::$instance = new self();
        }
        return self::$instance;
    }
}

修改2 (允许Serialize/Unserialize,并保证单例)

class Singleton {
    private static $instance = NULL;
    /** 不容许直接调用构造函数 */
    private function __construct() {
    }
    /** 不容许深度复制 */
    private function __clone() {
    }
    public function __wakeup() {
        self::$instance = $this;
    }
    /** 需要在单例切换的时候做清理工作 */
    public function __destruct() {
        self::$instance = NULL;
    }
    public static function getInstance() {
        if (NULL === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

修改2 存在问题

    $a = Singleton::getInstance();  
    $a = unserialize(serialize($a));  
    var_dump($a === Singleton::getInstance());  
    //bool(false) 

修改2 原因

在我们调用unserialize(serialize($a))的时候, 在serialize之前, PHP会首先尝试调用我们的类的实例$a的__sleep方法, 因为我们没有定义此方法, 所以跳过此步骤..

接下来, 在unserialize的时候, PHP在完成对象的创建以后, 会来调用新创建对象的__wakeup方法 , 在这里面, 我们释放了原有的self::$instance的引用, 改变成了新的对象.

这个时候, 原来的$a, 并不会被释放, 因为此时符号名a还保留着对$a(单例类的一个实例)的引用, 但此时$a所指的对象的引用计数已经-1, 变成了1, (应该还要了解到, 此时, 还会对Object Store中的对象引用计数-1, 也变为了1)

最后, 我们把得到的新对象给$a赋值, OK, 关键的时候来了, 这个时候, 因为我们重新对$a赋值, 所以$a会释放之前所值向的zval的引用, 造成了此时这个zval的引用计数变为了零, 于是PHP就会释放这个zval, 也就会调用了Singleton的析构函数, 在这个析构函数中, 我们释放了静态实例$instance..

最终修改方案

class Singleton {
    private static $instance = NULL;
    /** 不容许直接调用构造函数 */
    private function __construct() {
    }
    /** 不容许深度复制 */
    private function __clone() {
    }
    public function __wakeup() {
        self::$instance = $this;
    }
    /** 需要在单例切换的时候做清理工作 */
    public function __destruct() {
        //只做清理工作
    }
    public static function getInstance() {
        if (NULL === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

如若时光萧瑟去丶
111 声望9 粉丝

weakChickenPeng.