在取消序列化时选择实例



我正在编写一个类似于Java货币的PHP类:

该类的设计使得任何给定的货币永远不会有一个以上的Currency实例。因此,没有公共构造函数。您可以使用getInstance方法获得Currency实例。

使用PHP保持一个实例化对象的静态数组,并在getInstance()中执行查找以返回一个现有实例,或者在需要时实例化它,这是相当容易的。

问题来自于序列化。即使我实现Serializable,我也没有办法选择我想在unserialize()中返回的实例,因为这个对象在此时已经实例化了,而且因为在PHP中你不能重写$this:

class Currency implements Serializable
{
    public function getInstance()
    {
        // ...
    }
    public function serialize()
    {
        // ...
    }
    public function unserialize($data)
    {
        // At this point, the object is already instantiated,
        // so I can't just return self::getInstance(),
        // and can't override $this
    }
}

是否有任何技术解决方案来选择在反序列化时返回的实例?

—edit—

这是我试图解决的用例:

$euro = Currency::getInstance('EUR');
assert($euro === unserialize(serialize($euro));

我知道我可以用unserialize()建立一个类似的对象,但我想知道是否有可能获得相同的对象。

td;dr;不,您不能在PHP中本地做到这一点,因为unserialize()不支持与Java相同的readResolve()功能。

在Java中,在反序列化时,创建对象的新实例,恢复其状态,然后在类上调用readResolve()(如果它存在)。readResolve()接受新创建的反序列化对象,然后将其解析为另一个对象(如果需要),此时解析后的对象就是反序列化返回的对象。如果readResolve()返回的对象与提供给它的对象不同,那么最初创建的新实例将被垃圾收集。

在PHP中,unserialize()中没有这样的钩子。但是,您可以实现一个变通方法,定义您自己的resolve方法来模拟readResolve(),但是您需要在unserialize()之后调用它。

class Currency implements Serializable {
    private $currencyCode;
    private static $instances = array();
    private function __construct( $currencyCode) {
        $this->currencyCode = $currencyCode;
    }
    public static function getInstance( $currencyCode) {
        if( !isset( self::$instances[$currencyCode])) {
            self::$instances[$currencyCode] = new Currency( $currencyCode);
        }
        return self::$instances[$currencyCode];
    }
    public function serialize() {
        return serialize( $this->currencyCode);
    }
    public function unserialize( $data) {
        $this->currencyCode = unserialize( $data);
    }
    public static function resolve( $obj) {
        $new = self::getInstance( $obj->currencyCode);
        if( $new !== $obj) {
            unset( $obj);
        }
        return $new;
    }
}
$euro = Currency::getInstance('EUR');
assert($euro === Currency::resolve( unserialize(serialize($euro))));

当然,没有什么可以阻止某人不调用Currency::resolve(),然后您将最终获得多个给定货币代码的对象。

最新更新