I came across a quite tough problem to solve regarding objects implementing the Serializable
interface. Let's take an example:
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
We can see in this example, that when using the built-in PHP serialization feature (whether or not we use the __sleep()
function), the cross references between A
& B
are preserved (r:1;
).
However, if we want to enforce the use of the Serializable interface, the problem arises:
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
Because each object serialization is independently managed, there is no global way to see whether an object has already been serialized, to create a reference to it. The serialize()
functions are then called in an infinite loop.
Is there a good workaround for this problem? A pattern to use for the serialize()
functions?
Here is a trick:
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
This problem has been named cyclic object references by the Doctrine Project in their documentation on Serializing entities:
Serializable does not work well with any potential cyclic object references (at least we did not find a way yet, if you did, please contact us).
So, as far as I know, there is currently no generic solution to this problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With