我遇到了一个很难解决的关于实现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
&B
保存(r: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()
函数的模式?
这里有一个技巧:
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
这个问题被命名为循环对象引用由Doctrine Project在他们的文档中序列化实体:
Serializable不能很好地处理任何潜在的循环对象参考资料(至少我们还没有找到一个方法,如果你找到了,请联系我们)。
因此,据我所知,目前还没有通用的解决方案来解决这个问题。
标准PHP序列化提供了两种引用:递归和递归ref.
当您实现Serializable
接口时,只要您提供一个存储,您就可以将其扩展到多个对象和序列化步骤。例如,当内存中的每个对象都有一个标识符时,中央序列化服务可以在序列化时填充这些标识符,并在反序列化时以不同的顺序解包。
服务可以决定从存储中获取具有多个引用的对象-或者如果已经未序列化-运行时对象。由于服务是中心的,它可以很容易地决定,并且Serializable
方法可以直接使用该服务。
这也允许循环引用。但是,由于映射的关系,您需要自己实现它。
从字面上看,理论项目实际上已经有了这个。它的对象序列化形式是数据库本身。只是打开一点视图,确定这不是你想要的