使用PHP序列化维护对象关系

时间:2011-08-05 10:20:05

标签: php oop serialization relationship

我遇到了一个非常难以解决的关于实现Serializable接口的对象的问题。我们来举个例子:

class A {
    public $b;
}

class B {
    public $a;
}

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

$a->b = $b;
$b->a = $a;

echo serialize($a); // O:1:"A":1:{s:1:"b";O:1:"B":1:{s:1:"a";r:1;}}
echo serialize($b); // O:1:"B":1:{s:1:"a";O:1:"A":1:{s:1:"b";r:1;}}

$a = unserialize(serialize($a));
var_export($a === $a->b->a); // true

我们可以在这个例子中看到,当使用内置的PHP序列化功能时(无论我们是否使用__sleep()函数),A&之间的交叉引用。保留Br:1;)。

但是,如果我们想强制使用Serializable接口,则会出现问题:

class A implements Serializable  {
    public $b;

    public function serialize() { return serialize($this->b); }
    public function unserialize($s) { $this->b = unserialize($s); }
}

class B implements Serializable {
    public $a;

    public function serialize() { return serialize($this->a); }
    public function unserialize($s) { $this->a = unserialize($s); }
}

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

$a->b = $b;
$b->a = $a;

echo serialize($a); // infinite loop crashes PHP

因为每个对象序列化都是独立管理的,所以没有全局方法可以查看对象是否已经被序列化,以创建对它的引用。然后在无限循环中调用serialize()函数。

这个问题有一个很好的解决方法吗?用于serialize()函数的模式?

3 个答案:

答案 0 :(得分:3)

这是一个技巧:

function inStack( $cls, $func ) {
    $backTrace = debug_backtrace();
    for( $i = 2; $i < count( $backTrace ); ++$i  ) {
        if( isset( $backTrace[$i][ 'class' ] ) && $backTrace[$i][ 'class' ] == $cls &&
            isset( $backTrace[$i][ 'function' ] ) && $backTrace[$i][ 'function' ] == $func )
            return true;
    }
    return false;
}

class A implements Serializable  {
    public $b;

    public function serialize() {
        if( inStack( 'A', 'serialize' ) ) return '';
        return serialize( $this->b );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $b = unserialize( $s );
        if( $b !== null ) {
            $this->b = $b;
            $this->b->a = $this;
        }
    }
}

class B implements Serializable {
    public $a;

    public function serialize() {
        if( inStack( 'B', 'serialize' ) ) return '';
        return serialize( $this->a );
    }
    public function unserialize($s) {
        if( $s == '' ) return null;
        $a = unserialize( $s );
        if( $a !== null ) {
            $this->a = $a;
            $this->a->b = $this;
        }
    }
}

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

$a->b = $b;
$b->a = $a;

$a = unserialize( serialize( $a ) );
var_dump( $a === $a->b->a ); //true

答案 1 :(得分:3)

Doctrine Project在Serializing entities的文档中将此问题命名为循环对象引用

  

Serializable不适用于任何潜在的循环对象   参考(至少我们还没有找到方法,如果你这样做,请   联系我们。)

因此,据我所知,目前还没有针对此问题的通用解决方案。

答案 2 :(得分:3)

标准PHP序列化提供了两种引用,它们只能按顺序方式对单个序列化调用起作用:递归和递归引用。

实施Serializable界面时,只要提供一个商店,就可以在多个对象和序列化步骤中扩展它。例如,当内存中的每个对象都有一个标识符时,可以在序列化中填充中心的selalationization服务,并在反序列化时以不同的顺序解压缩。

服务可以负责决定从存储中获取具有多个引用的对象 - 或者如果已经是未序列化的 - 则运行时对象。由于服务是核心,因此可以轻松决定,Serializable方法只能使用此服务。

这也允许循环引用。但是,由于映射,您需要自己实现它。

接受这个词,该学说项目实际上已经有了这个。它的对象序列化形式就是数据库本身。只是打开一点视图,确定这不是你想要的。