2011-08-05 7 views
5

Serializableインターフェイスを実装するオブジェクトに関して解決するのはかなり難しい問題でした。のは、例を見てみましょう:PHPのシリアル化でオブジェクトの関係を維持する

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()機能に使用するパターン?ここで

+0

これは素晴らしいPHP *の "機能"です。それを解決する方法を教えてもらえません。参照を割り当てようとしましたが( '$ a-> b =&$ b')、それも失敗しました。 –

+0

これは純粋に予測可能な動作ですが、私はその解決策がユーザーコードにあると思いますが、今のところ回避策を見つけられません。 – Benjamin

答えて

3

この問題はSerializing entities上のドキュメントに教義プロジェクトによって巡回オブジェクト参照を命名されました:

シリアライズは、あらゆる潜在的でうまく動作しません。周期的なオブジェクト の参照(少なくとも私たちは方法を見つけられませんでした、あなたがした場合は お問い合わせください)。

私の知る限り、この問題の一般的な解決策はありません。

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 
+0

うわー!素晴らしい技術的解決策ですが、クラス名だけに基づいた 'inStack()'テストではなく、同じクラスの複数のインスタンスを直列化しなければならない場合は壊れますか? – Benjamin

+0

@ベンジャミン 'A :: serialize'の中から' inStack( 'A'、 'serialize')を呼び出すと、A :: serializeを再帰的に呼び出すときだけtrueを返します。私はinStack()自体と現在のA :: serialize呼び出しをバイパスするために、インデックス2から始まるトレース配列を反復処理します。 – nobody

+0

私はまだ安定した動作を提供するためにデバッグ機能を使用することに非常に自信を持っていません:S – Benjamin

3

標準のPHPシリアル化では、1回のシリアライズコールに対して、再帰と再帰参照の2つの種類があります。

Serializableインターフェイスを実装すると、1つのストアを提供している限り、複数のオブジェクトとシリアライゼーションステップにまたがる可能性があります。例えば、メモリ内の各オブジェクトが識別子を有する場合、シリアル化に際して、中央のセルラ化サービスにこれらを詰め込み、シリアル化解除時に異なる順序でアンパックすることができる。

サービスは、ストアから複数の参照があるオブジェクトをフェッチするか、すでにシリアル化されていない場合はランタイムオブジェクトをフェッチすることができます。サービスは中心的なものなので、簡単に決定でき、Serializableのメソッドでこのサービスを利用できます。

これにより、循環参照が可能になります。ただし、マッピングのためにこれを独自に実装する必要があります。

doctrineプロジェクトは実際にこれを既に持っています。オブジェクト直列化の形式は、データベースそのものです。ビューを少しだけ開いて、これはあなたが探しているものではありません。

関連する問題