PHPプログラミングでは、シリアル化関数を使用してオブジェクトまたは配列を文字列に変換し、保存または転送できるようにします。ただし、オブジェクトをシリアル化すると、他のオブジェクトを参照するオブジェクトに属性があり、これらのオブジェクト間に相互参照関係がある場合(たとえば、aはbを参照し、bはaを参照します)、無限の再帰を引き起こし、最終的にスクリプトクラッシュにつながる可能性があります。
この記事では、PHPのシリアル化機能を使用する際の無限の再帰のtrapを回避する方法について説明します。
PHPのシリアル化関数は、PHP値を保存または転送できる形式に変換します。通常、オブジェクトまたは配列の永続的なストレージに使用されます。例えば:
<?php
$array = array("name" => "GitBox", "url" => "https://gitbox.net");
$serialized = serialize($array);
echo $serialized;
?>
通常、無限の再帰のtrapは、オブジェクト間に円形の参照がある場合に発生します。例えば:
<?php
class Node {
public $name;
public $child;
public function __construct($name) {
$this->name = $name;
}
public function setChild($child) {
$this->child = $child;
}
}
$node1 = new Node("Node 1");
$node2 = new Node("Node 2");
$node1->setChild($node2);
$node2->setChild($node1);
$serialized = serialize($node1); // これにより、無限の再帰が発生します
?>
上記のコードでは、 $ node1と$ node2が互いを参照しています。 Serialize($ node1)が呼ばれると、PHPは$ node1オブジェクトのシリアル化を開始します。これにより、 node1の子プロパティがシリアル化され、子プロパティは$ node2を指します。その後、PHPは$ node2のシリアル化を継続し、その後$ node1のシリアル化に戻り、無限の再帰をもたらし、メモリオーバーフローになります。
Serializeを使用するときに無限の再帰のtrapに陥ることを避けるために、次の方法をとることができます。
PHPは魔法の方法を提供します__睡眠を提供します。これにより、どのプロパティをシリアル化するかを指定できます。オブジェクト属性が再帰を引き起こす場合、 __睡眠法を使用してこれらの属性を除外します。
<?php
class Node {
public $name;
public $child;
public function __construct($name) {
$this->name = $name;
}
public function setChild($child) {
$this->child = $child;
}
public function __sleep() {
return ['name']; // シリアル化のみ $name 財産,円形の参照は避けてください
}
}
$node1 = new Node("Node 1");
$node2 = new Node("Node 2");
$node1->setChild($node2);
$node2->setChild($node1);
$serialized = serialize($node1); // ここで再帰は起こりません
echo $serialized;
?>
Sleepの属性のリストを返すことにより、どの属性がシリアル化されるかを制御することができ、したがって再帰を回避できます。
splobjectStorageは、オブジェクトを保存するために特別に使用されるクラスであり、保存時にオブジェクトの重複によって引き起こされる再帰の問題を回避できます。再帰の問題を回避するために使用できます。
<?php
class Node {
public $name;
public $child;
public function __construct($name) {
$this->name = $name;
}
public function setChild($child) {
$this->child = $child;
}
}
$node1 = new Node("Node 1");
$node2 = new Node("Node 2");
$node1->setChild($node2);
$node2->setChild($node1);
$storage = new SplObjectStorage();
$storage->attach($node1);
$storage->attach($node2);
$serialized = serialize($storage); // ここで再帰は起こりません
echo $serialized;
?>
splobjectStorageは、オブジェクトへの参照を内部的に追跡するため、円形の参照によって引き起こされる無限の再帰を回避できます。
場合によっては、シリアル化されたオブジェクトを復元するときに追加のチェックを行うことをお勧めします。 PHPは__wakeup Magicメソッドを提供し、オブジェクトを脱出するときにいくつかの操作を実行できるようにします。
<?php
class Node {
public $name;
public $child;
public function __construct($name) {
$this->name = $name;
}
public function setChild($child) {
$this->child = $child;
}
public function __wakeup() {
if ($this->child === $this) {
$this->child = null; // 脱介入中の円形の参照を防ぎます
}
}
}
$node1 = new Node("Node 1");
$node2 = new Node("Node 2");
$node1->setChild($node2);
$node2->setChild($node1);
$serialized = serialize($node1);
echo $serialized;
$unserialized = unserialize($serialized); // ここでは、円形の参照が自動的にクリアされます
?>
__wakeupメソッドでは、ループの参照を検出してクリアして、再び脱色時に再び再帰を回避します。
これらの方法により、PHPシリアル化によって引き起こされる可能性のある無限の再帰問題を効果的に回避でき、オブジェクトを処理する際に循環参照のためにプログラムがクラッシュしないようにします。この記事が、PHPでシリアル化関数を使用する際に再帰的なトラップを避ける方法をよりよく理解するのに役立つことを願っています。ご質問がある場合は、引き続き議論してください!