【PHP8.x】Dom\EntityReference::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、unserialize()関数によってオブジェクトが復元される際に、そのオブジェクトの状態を再確立するために自動的に呼び出されるマジックメソッドです。しかし、Dom\EntityReferenceクラスを含む多くのDOM関連クラスのインスタンスは、シリアライズおよびデシリアライズがサポートされていません。これは、DOMオブジェクトがドキュメント全体の構造や親子関係といった文脈に強く依存しており、オブジェクト単体で状態を完全に保存・復元することが困難であるためです。この設計上の制約から、シリアライズされたDom\EntityReferenceオブジェクトをunserialize()関数で復元しようとすると、この__wakeupメソッドが内部で実行され、常にDom\Exception例外をスローします。したがって、このメソッドは開発者が能動的に利用するものではなく、サポートされていないデシリアライズ操作が試みられた場合に、エラーを発生させてプログラムの意図しない動作を防ぐという重要な役割を担っています。』
構文(syntax)
1public function __wakeup(): void
引数(parameters)
引数なし
引数はありません
戻り値(return)
void
__wakeup メソッドは、オブジェクトが unserialize() によって再構築された後に自動的に呼び出されます。このメソッドは、オブジェクトの初期化や関連リソースの再設定などの後処理を行うために使用され、戻り値はありません。
サンプルコード
PHP __wakeup バイパスの試み
1<?php 2 3/** 4 * __wakeup() のバイパス脆弱性(CVE-2016-7124)を説明するためのデモクラスです。 5 * この脆弱性は古いPHPバージョンに存在し、PHP 7.1以降では修正されています。 6 */ 7class VulnerableObject 8{ 9 // オブジェクトのプロパティ 10 public string $data; 11 12 public function __construct(string $data) 13 { 14 $this->data = $data; 15 echo "コンストラクタが呼ばれました。データ: {$this->data}" . PHP_EOL; 16 } 17 18 /** 19 * unserialize() の実行後に自動的に呼び出されるマジックメソッドです。 20 * オブジェクトの状態を復元する処理(例: データベース接続の再確立)を記述します。 21 */ 22 public function __wakeup(): void 23 { 24 echo "__wakeup() が呼ばれました。オブジェクトの状態を復元します。" . PHP_EOL; 25 // 復元時の初期化処理など 26 $this->data = 'restored'; 27 } 28 29 /** 30 * オブジェクトが破棄されるときに自動的に呼び出されるマジックメソッドです。 31 */ 32 public function __destruct() 33 { 34 echo "__destruct() が呼ばれました。クリーンアップ処理を実行します。" . PHP_EOL; 35 } 36} 37 38// 1. 通常のシリアライズとアンシリアライズの動作を確認します。 39echo "--- 1. 通常の動作 ---" . PHP_EOL; 40$object = new VulnerableObject('original'); 41$serialized_string = serialize($object); 42echo "シリアライズされた文字列: " . $serialized_string . PHP_EOL; 43 44// 文字列からオブジェクトを復元します。このとき __wakeup() が呼ばれます。 45$unserialized_object = unserialize($serialized_string); 46echo "復元後のデータ: " . $unserialized_object->data . PHP_EOL; 47echo PHP_EOL; 48 49 50// 2. __wakeup() のバイパスを試みます。 51echo "--- 2. __wakeup() バイパスの試み (PHP 8での動作) ---" . PHP_EOL; 52 53// シリアライズされた文字列のプロパティ数を、実際の数(1)より大きい数(2)に改ざんします。 54// O:16:"VulnerableObject":1:{...} -> O:16:"VulnerableObject":2:{...} 55$malicious_string = str_replace(':1:{', ':2:{', $serialized_string); 56echo "改ざんされた文字列: " . $malicious_string . PHP_EOL; 57 58/* 59 * 古いPHPでは、この改ざんによって __wakeup() の呼び出しがスキップされ、 60 * 不完全なオブジェクトが生成されてしまう脆弱性がありました。 61 * 62 * PHP 8 (7.1以降) ではこの脆弱性は修正されています。 63 * プロパティ数が一致しないため unserialize() は失敗し、Notice が発生して false を返します。 64 * その結果、__wakeup() はもちろん、__destruct() も呼ばれることはありません。 65 */ 66echo "改ざんされた文字列をアンシリアライズします..." . PHP_EOL; 67$result = unserialize($malicious_string); 68 69var_dump($result); 70 71echo "スクリプトの最後" . PHP_EOL; 72 73?>
このPHPコードは、unserialize()関数が実行された際に自動的に呼び出されるマジックメソッド__wakeup()の動作を説明するものです。このメソッドは引数を取らず、何も値を返しません(void)。主な役割は、文字列から復元されたオブジェクトの状態を初期化することです。
サンプルコードでは、まずオブジェクトをserialize()で文字列へ変換し、それをunserialize()でオブジェクトに復元する通常の流れを示しています。この復元プロセスで__wakeup()が自動的に呼び出され、オブジェクトのプロパティが初期化される様子がわかります。
次に、古いPHPバージョンに存在した__wakeup()メソッドを意図的に呼び出させないようにする「バイパス」という脆弱性を試みています。これは、シリアライズされた文字列内のプロパティ数を不正に書き換えることで行われます。しかし、この脆弱性はPHP 7.1以降では修正済みです。そのため、PHP 8でこのコードを実行すると、不正な文字列によるunserialize()は失敗し、結果としてfalseが返されます。これにより、現在のPHPではこの脆弱性の影響を受けず、__wakeup()が安全に保護されていることが確認できます。
__wakeupは、unserialize関数でオブジェクトを復元する際に自動的に呼び出される特殊なメソッドです。サンプルコードで示されている、シリアライズされた文字列を改ざんして__wakeupの実行を回避する脆弱性は、PHP 7.1以降では修正済みです。現在のPHPでは不正な文字列でunserializeを実行すると、処理が失敗しオブジェクトは生成されません。最も重要な注意点は、ユーザー入力など信頼できない外部のデータをunserializeしないことです。これにより、意図しないコードが実行されるなどの深刻なセキュリティ問題につながる危険性があります。unserialize関数の戻り値は必ず検証し、処理が失敗した場合も想定してコードを記述することが不可欠です。
PHP __wakeupでオブジェクトを再接続する
1<?php 2 3/** 4 * __wakeupマジックメソッドの使用例を示すクラス 5 * 6 * オブジェクトがデシリアライズ(unserialize)される際に、 7 * データベース接続のような失われたリソースを再初期化する目的で使われます。 8 */ 9class WakeupExample 10{ 11 /** 12 * オブジェクトの現在の状態を示すプロパティ 13 * @var string 14 */ 15 private string $status; 16 17 /** 18 * シリアル化する際に保存されるデータ 19 * @var string 20 */ 21 private string $data; 22 23 public function __construct(string $initialData) 24 { 25 $this->data = $initialData; 26 $this->reconnect(); // 初期化時に接続 27 } 28 29 /** 30 * 現在の状態を取得します。 31 * @return string 32 */ 33 public function getStatus(): string 34 { 35 return $this->status; 36 } 37 38 /** 39 * 接続を確立する処理をシミュレートします。 40 */ 41 private function reconnect(): void 42 { 43 echo "接続を確立しました。\n"; 44 $this->status = 'Connected'; 45 } 46 47 /** 48 * serialize()がコールされた際に呼び出されます。 49 * シリアル化したいプロパティ名の配列を返します。 50 * ここでは、一時的な状態である$statusは含めません。 51 * 52 * @return string[] 53 */ 54 public function __sleep(): array 55 { 56 echo "オブジェクトをシリアル化します... (接続は切断されます)\n"; 57 $this->status = 'Disconnected'; 58 return ['data']; 59 } 60 61 /** 62 * unserialize()がコールされた際に呼び出されます。 63 * このメソッドで、オブジェクトの状態を再構築します。 64 * 65 * @return void 66 */ 67 public function __wakeup(): void 68 { 69 echo "オブジェクトがデシリアライズされました。再接続します...\n"; 70 $this->reconnect(); 71 } 72} 73 74// 1. オブジェクトを生成 75$obj = new WakeupExample('some important data'); 76echo "生成直後: " . $obj->getStatus() . "\n\n"; 77 78// 2. オブジェクトをシリアライズ(文字列に変換) 79// この過程で __sleep() が呼び出され、接続が切断された状態になる 80$serializedObj = serialize($obj); 81echo "シリアライズ後の文字列: " . $serializedObj . "\n\n"; 82 83// 3. 文字列からオブジェクトをデシリアライズ(復元) 84// この過程で __wakeup() が呼び出され、自動的に再接続される 85$unserializedObj = unserialize($serializedObj); 86 87// 4. 復元されたオブジェクトの状態を確認 88echo "復元後: " . $unserializedObj->getStatus() . "\n";
__wakeupは、unserialize()関数によってオブジェクトが復元(デシリアライズ)される際に、自動的に呼び出される特別なメソッド(マジックメソッド)です。このメソッドの主な目的は、オブジェクトがシリアライズ(文字列化)される過程で失われたリソースを再初期化することです。例えば、データベース接続やファイルハンドルなどがこれにあたります。
このサンプルコードでは、まずserialize()関数でオブジェクトを文字列に変換します。このとき、対となる__sleep()メソッドが実行され、接続状態を模したプロパティが「切断」状態になります。
次に、unserialize()関数を使って、この文字列からオブジェクトを復元します。この瞬間に__wakeup()メソッドが自動で実行されます。コード内では、このメソッドがデータベース接続などを再確立する処理(reconnect())を呼び出しているため、復元されたオブジェクトは再び正常に利用できる状態に戻ります。
__wakeupメソッドは引数を受け取らず、また戻り値もありません(void)。その役割は、オブジェクトの内部状態を復元後の適切な状態に整えることに特化しています。
__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動的に呼び出される特殊なメソッドであり、開発者が直接呼び出すものではありません。対になる__sleepメソッドが保存前の準備処理であるのに対し、__wakeupは復元後の初期化処理を担います。主にデータベース接続やファイルハンドルなど、保存できないリソースを再確立する目的で利用されます。最も重要な注意点はセキュリティです。信頼できない文字列をunserialize()すると、深刻な脆弱性につながる危険があるため、入力データは必ず検証してください。なお、PHP 7.4からは、より新しい__serialize()と__unserialize()メソッドの利用が推奨されています。