Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】Dom\Entity::__wakeup()メソッドの使い方

__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

『__wakeupメソッドは、Dom\Entityオブジェクトがアンシリアライズされるのを防ぐために、意図的に例外をスローするメソッドです。このメソッドは、PHPの「マジックメソッド」の一つであり、unserialize()関数によってオブジェクトが復元される際に自動的に呼び出されます。Dom\EntityのようなDOM関連のオブジェクトは、XMLドキュメントの内部構造を表す複雑なリソースに依存しており、その状態を単純な文字列として保存し、完全に復元することはできません。もしアンシリアライズが許可されてしまうと、内部状態に不整合が生じ、予期せぬ動作を引き起こす不正なオブジェクトが生成される危険性があります。そのため、Dom\Entityクラスでは、__wakeupメソッドを実装し、呼び出された際に必ずDom\Exceptionをスローする仕組みになっています。これにより、開発者が誤ってこれらのオブジェクトをアンシリアライズしようとした場合に処理を中断させ、プログラムの堅牢性を保証する役割を果たしています。』

構文(syntax)

1public Dom\Entity::__wakeup(): void

引数(parameters)

引数なし

引数はありません

戻り値(return)

void

__wakeupメソッドは、オブジェクトがデシリアライズ(復元)された後に呼び出されます。このメソッドは、オブジェクトの状態を初期化したり、デシリアライズ後の処理を行ったりするために使用されます。戻り値はありません。

サンプルコード

PHP 8 __wakeup の挙動とバイパス解説

1<?php
2
3namespace Dom;
4
5/**
6 * Dom\Entity クラスの__wakeupメソッドの動作を示すサンプルコード。
7 * PHP 8において、__wakeupの呼び出しと、歴史的な__wakeupバイパスの概念を説明します。
8 */
9class Entity
10{
11    /** @var int エンティティのID */
12    public int $id;
13
14    /** @var string エンティティの名前 */
15    public string $name;
16
17    /**
18     * コンストラクタ
19     * オブジェクトが生成される際に一度だけ呼び出されます。
20     *
21     * @param int $id エンティティのID
22     * @param string $name エンティティの名前
23     */
24    public function __construct(int $id, string $name)
25    {
26        $this->id = $id;
27        $this->name = $name;
28        echo "Dom\\Entity オブジェクト生成: ID {$this->id}, Name: {$this->name}\n";
29    }
30
31    /**
32     * __wakeup マジックメソッド
33     * unserialize() 関数によってオブジェクトがデシリアライズされる直前に呼び出されます。
34     * このメソッドは引数を受け取らず、何も返しません (void)。
35     * 主に、シリアライズ中に切断されたデータベース接続やファイルハンドルなどの
36     * 外部リソースを再確立するために使用されます。
37     *
38     * @return void
39     */
40    public function __wakeup(): void
41    {
42        echo "Dom\\Entity::__wakeup が呼び出されました! (ID: {$this->id})\n";
43        // ここに、オブジェクトの内部状態を再構築したり、
44        // 外部リソースを再接続するロジックを記述します。
45        // 例: $this->dbConnection = Database::reconnect();
46    }
47
48    /**
49     * オブジェクトが文字列として扱われる際の挙動を定義します。
50     * デバッグ出力などで便利です。
51     *
52     * @return string
53     */
54    public function __toString(): string
55    {
56        return "Dom\\Entity [ID: {$this->id}, Name: {$this->name}]";
57    }
58}
59
60// --- __wakeup メソッドの通常の動作確認 ---
61
62echo "--- 1. 通常のデシリアライズで __wakeup が呼び出される例 (PHP 8) ---\n";
63
64// Dom\Entity オブジェクトを生成
65$originalEntity = new Entity(101, "Test Entity");
66
67// オブジェクトをシリアライズ(文字列に変換)
68$serializedEntity = serialize($originalEntity);
69echo "シリアライズされたデータ: " . $serializedEntity . "\n";
70
71echo "デシリアライズ中...\n";
72// シリアライズされた文字列からオブジェクトを復元
73// この操作により、Dom\Entity::__wakeup メソッドが呼び出されます。
74$unserializedEntity = unserialize($serializedEntity);
75
76if ($unserializedEntity instanceof Dom\Entity) {
77    echo "デシリアライズされたオブジェクト: " . $unserializedEntity . "\n\n";
78} else {
79    echo "デシリアライズ失敗または期待しないオブジェクトが復元されました。\n\n";
80}
81
82
83// --- __wakeup バイパスの概念 (PHP 7.0 未満での挙動の解説) ---
84
85echo "--- 2. PHP 7.0 未満における '__wakeup' バイパスの概念 (PHP 8 では適用外) ---\n";
86echo "PHP 7.0 未満のバージョンでは、以下のようなセキュリティ脆弱性が存在しました。\n";
87echo "シリアライズされた文字列内のオブジェクトのプロパティ数(例: 'O:9:\"Dom\\Entity\":2:{...}' の '2' の部分)が、\n";
88echo "実際のオブジェクトのプロパティ数よりも『少ない』場合、__wakeup メソッドは『スキップ』されました。\n";
89echo "これはPHPオブジェクトインジェクション (POI) 攻撃の一環として、\n";
90echo "悪意のあるユーザーがオブジェクトの初期化ロジック (例えば、特定のプロパティのチェックやリソースの再接続) を\n";
91echo "意図的に回避するために悪用されることがありました。\n\n";
92
93echo "しかし、PHP 7.0 以降 (PHP 8 を含む) では、この挙動は修正されました。\n";
94echo "現在では、たとえシリアライズされた文字列内のプロパティ数が実際の数と異なっていても、\n";
95echo "__wakeup メソッドは常に呼び出されます。\n";
96echo "したがって、PHP 8 ではプロパティ数の操作による__wakeupのスキップはできません。\n\n";
97
98// 例として、プロパティ数を意図的に '0' に設定したシリアライズ文字列
99// この文字列はPHP 7.0 未満であれば __wakeup がスキップされました。
100// PHP 8では__wakeupが呼び出され、プロパティが適切にセットされないオブジェクトが作成されます。
101$maliciousSerializedString = 'O:9:"Dom\Entity":0:{}';
102echo "不正なシリアライズ文字列 (プロパティ数 0): " . $maliciousSerializedString . "\n";
103
104echo "不正な文字列でデシリアライズ中 (PHP 8での挙動):\n";
105$bypassedEntity = @unserialize($maliciousSerializedString); // @でエラー出力を抑制
106
107if ($bypassedEntity instanceof Dom\Entity) {
108    echo "デシリアライズ成功。しかし、プロパティは初期化されていません。\n";
109    // PHP 8ではここで 'Dom\Entity::__wakeup が呼び出されました!' と表示されるはずです。
110    // その後、`id` プロパティが設定されていないため、アクセスするとエラーになる可能性があります。
111    echo "注意: PHP 8 では、上記で Dom\\Entity::__wakeup が呼び出されていることを確認してください。\n";
112    echo "プロパティが初期化されていないため、オブジェクトの状態は不完全です。\n";
113    // idにアクセスすると、Undefined property の警告/エラーが発生します。
114    // echo "デシリアライズされたオブジェクトのID: " . $bypassedEntity->id . "\n";
115} else {
116    echo "デシリアライズ失敗または期待しないオブジェクトが復元されました。\n";
117}
118
119echo "\n--- まとめ ---\n";
120echo "PHP 8 では、不正なプロパティ数による '__wakeup' メソッドの意図的なスキップは不可能です。\n";
121echo "セキュリティの観点から最も重要なのは、信頼できないソースからの入力に対して\n";
122echo "unserialize() 関数を使用しないことです。代替として JSON や YAML など、より安全なデータ形式を検討してください。\n";
123
124?>

PHPのDom\Entity::__wakeupメソッドは、オブジェクトがunserialize()関数によって文字列から復元される直前に自動的に呼び出される特殊なマジックメソッドです。このメソッドは引数を受け取らず、何も返さない(void)設計となっており、主にシリアライズ中に切断されたデータベース接続やファイルハンドルなどの外部リソースを再確立するために使用されます。サンプルコードでは、オブジェクトをシリアライズし、その後デシリアライズする過程で__wakeupメソッドが正常に呼び出される様子を示しています。

かつてPHP 7.0未満のバージョンでは、シリアライズされた文字列内のオブジェクトのプロパティ数が実際のオブジェクトのプロパティ数よりも少ない場合、__wakeupメソッドがスキップされるというセキュリティ上の脆弱性(__wakeupバイパス)が存在しました。これはPHPオブジェクトインジェクション攻撃に利用される可能性がありましたが、PHP 7.0以降、特にPHP 8ではこの挙動が修正されています。現在では、プロパティ数の操作に関わらず__wakeupメソッドは常に呼び出されます。そのため、PHP 8では不正なプロパティ数による意図的な__wakeupのスキップはできません。

セキュリティの観点から、unserialize()関数は信頼できないソースからの入力には決して使用しないことが強く推奨されます。代替として、JSONやYAMLなどのより安全なデータ形式を検討することが重要です。

__wakeupメソッドは、unserialize()でのオブジェクト復元直前に呼び出され、外部リソース再接続など初期化に利用されます。PHP 7.0以降(PHP 8含む)では、過去に存在したプロパティ数操作による__wakeupバイパスの脆弱性は修正され、プロパティ数が異なっても__wakeupは常に呼び出されますので、この点での心配は不要です。しかし、unserialize()関数自体はPHPオブジェクトインジェクション(POI)といった深刻なセキュリティリスクを抱えるため、信頼できない入力には絶対に使用せず、JSONやYAMLなど安全なデータ形式を検討してください。

PHP __wakeup でオブジェクトを復元する

1<?php
2
3/**
4 * データベース接続を模倣し、__wakeup の動作を説明するクラス
5 */
6class DatabaseConnection
7{
8    /** @var string データベース接続情報 */
9    private string $dsn;
10
11    /** @var ?string 接続状態を示すプロパティ */
12    private ?string $connectionStatus = null;
13
14    /**
15     * コンストラクタ
16     * オブジェクト生成時にデータベースへ接続します。
17     *
18     * @param string $dsn
19     */
20    public function __construct(string $dsn)
21    {
22        $this->dsn = $dsn;
23        $this->connect();
24    }
25
26    /**
27     * __sleep マジックメソッド
28     * serialize() が実行される直前に呼び出されます。
29     * シリアライズしたいプロパティ名の配列を返す必要があります。
30     * ここでは、接続状態は保存せず、接続情報(dsn)のみを保存対象とします。
31     *
32     * @return string[]
33     */
34    public function __sleep(): array
35    {
36        echo "-> __sleep() が呼び出されました。接続を一時的に閉じます。" . PHP_EOL;
37        $this->connectionStatus = null; // 接続状態をクリア
38        return ['dsn']; // 'dsn' プロパティのみシリアライズする
39    }
40
41    /**
42     * __wakeup マジックメソッド
43     * unserialize() が実行された直後、オブジェクトが復元された際に呼び出されます。
44     * データベース接続の再確立など、リソースの再初期化処理をここで行います。
45     *
46     * @return void
47     */
48    public function __wakeup(): void
49    {
50        echo "-> __wakeup() が呼び出されました。データベースに再接続します。" . PHP_EOL;
51        $this->connect(); // 接続を再確立する
52    }
53
54    /**
55     * データベースへの接続を模倣するメソッド
56     */
57    private function connect(): void
58    {
59        $this->connectionStatus = "Connected to " . $this->dsn;
60        echo "データベース接続が確立しました。({$this->connectionStatus})" . PHP_EOL;
61    }
62
63    /**
64     * 現在の接続状態を取得します。
65     *
66     * @return string
67     */
68    public function getStatus(): string
69    {
70        return $this->connectionStatus ?? 'Not Connected';
71    }
72}
73
74// 1. オブジェクトを生成します (__construct が呼ばれます)
75echo "1. 新しいオブジェクトを生成します..." . PHP_EOL;
76$db = new DatabaseConnection("mysql:host=localhost;dbname=mydb");
77echo "生成後の状態: " . $db->getStatus() . PHP_EOL . PHP_EOL;
78
79// 2. オブジェクトをシリアライズ(文字列化)します (__sleep が呼ばれます)
80echo "2. オブジェクトをシリアライズします..." . PHP_EOL;
81$serializedDb = serialize($db);
82echo "シリアライズされたデータ: " . $serializedDb . PHP_EOL . PHP_EOL;
83
84// 3. シリアライズされた文字列からオブジェクトを復元します (__wakeup が呼ばれます)
85echo "3. オブジェクトを復元します..." . PHP_EOL;
86$unserializedDb = unserialize($serializedDb);
87echo "復元後の状態: " . $unserializedDb->getStatus() . PHP_EOL;
88
89?>

__wakeupメソッドは、unserialize()関数によって文字列からオブジェクトが復元された直後に、自動的に呼び出されるPHPの「マジックメソッド」です。このメソッドの主な目的は、オブジェクトが復元された際に必要な初期化処理や、失われたリソースの再確立を行うことです。例えば、シリアライズの過程で切断されたデータベース接続やファイルハンドルなどを、再び利用可能な状態に戻す処理を記述します。

サンプルコードのDatabaseConnectionクラスでは、serialize()関数でオブジェクトを文字列化する際に、データベース接続が一時的に切断されます。その後、unserialize()関数でオブジェクトが復元されると、__wakeupメソッドが自動で実行され、内部でデータベースへの再接続処理を呼び出します。これにより、オブジェクトは再びデータベースと通信できる正常な状態に戻ります。

このように、__wakeupはオブジェクトの状態を完全に復元するための重要な役割を担います。このメソッドは引数を受け取らず、オブジェクトの内部状態を整えるための手続きであるため、特定の値を返す必要はなく、戻り値はvoidとなります。

__wakeupは、unserialize()関数によって文字列からオブジェクトが復元された直後に自動的に呼び出される特殊なメソッドです。主な用途は、データベース接続の再確立など、シリアライズ(保存)できないリソースを復元時に再初期化することです。対になる__sleepメソッドで保存する情報を定義し、__wakeupでその情報をもとに復元処理を記述するのが一般的な使い方です。最も重要な注意点は、信頼できない外部からのデータをunserialize()しないことです。悪意のある文字列を渡されると、__wakeupが自動実行され、意図しない動作を引き起こす脆弱性の原因となるため、データの出所を常に検証する必要があります。

__wakeupによるオブジェクト整合性チェック

1<?php
2
3namespace App;
4
5/**
6 * __wakeup マジックメソッドは、オブジェクトがデシリアライズされた直後に呼び出されます。
7 * これは、PHP オブジェクトインジェクションのようなセキュリティ脆弱性において、
8 * 攻撃者がオブジェクトの状態を改ざんしようとする試みを防ぐための重要なフックとなります。
9 * 現代の PHP (7.0以降) では、__wakeup は常に呼び出されるため、単純な「wakeup バイパス」
10 * (プロパティ数の詐称によるスキップ) は機能しませんが、オブジェクトの整合性確保や
11 * リソースの再初期化の役割は依然として重要です。
12 */
13class VulnerableObject
14{
15    public string $data;
16    public string $secret = 'super_secret_value';
17
18    public function __construct(string $data)
19    {
20        $this->data = $data;
21        echo "✅ Object created with data: " . $this->data . "\n";
22    }
23
24    /**
25     * オブジェクトがデシリアライズされた直後に呼び出されます。
26     * ここでオブジェクトの整合性を検証したり、リソースを再接続したり、
27     * 安全なデフォルト値を設定するなどの初期化処理を行うことが推奨されます。
28     * これにより、デシリアライズされたオブジェクトが不正な状態になるのを防ぎます。
29     */
30    public function __wakeup(): void
31    {
32        echo "🚨 __wakeup() method called! Performing deserialization checks...\n";
33        // 例: 不正な状態を防ぐためのセキュリティチェック
34        // デシリアライズ時に 'secret' プロパティが改ざんされていないか確認し、
35        // もし改ざんされていれば安全なデフォルト値に戻します。
36        if ($this->secret !== 'super_secret_value') {
37            echo "⚠️ Secret value tampered during deserialization! Resetting to a safe value.\n";
38            $this->secret = 'super_secret_value'; // 強制的に安全な値に戻す
39        }
40        // 他の初期化処理やログ記録など
41    }
42
43    public function showSecret(): void
44    {
45        echo "🔒 Current secret: " . $this->secret . "\n";
46    }
47}
48
49// === サンプル実行 ===
50
51// 1. オブジェクトの生成とシリアライズ
52$originalObject = new VulnerableObject('sensitive_info');
53$originalObject->showSecret();
54echo "📦 Serializing object...\n";
55$serializedData = serialize($originalObject);
56echo "Serialized Data: " . $serializedData . "\n\n";
57
58// 2. オブジェクトのデシリアライズ (__wakeup が呼び出されることを確認)
59echo "🔓 Deserializing object...\n";
60$deserializedObject = unserialize($serializedData);
61$deserializedObject->showSecret();
62echo "\n";
63
64// 3. 攻撃者がシリアライズデータを改ざんしてプロパティを変更した場合のシミュレーション
65// (PHP 7.0以降では __wakeup は常に呼び出されるため、改ざんされたプロパティを元に戻すことができます)
66echo "😈 Simulating malicious deserialization (attempting to modify 'secret').\n";
67// シリアライズデータを手動で改ざんし、secretプロパティの値を変更する
68// オリジナル: O:17:"App\\VulnerableObject":2:{s:4:"data";s:16:"sensitive_info";s:6:"secret";s:21:"super_secret_value";}
69// 改ざん後:   O:17:"App\\VulnerableObject":2:{s:4:"data";s:16:"sensitive_info";s:6:"secret";s:13:"hacked_secret";}
70$maliciousSerializedData = 'O:17:"App\\VulnerableObject":2:{s:4:"data";s:16:"sensitive_info";s:6:"secret";s:13:"hacked_secret";}';
71echo "Malicious Data: " . $maliciousSerializedData . "\n";
72
73$maliciousObject = unserialize($maliciousSerializedData);
74$maliciousObject->showSecret(); // __wakeup() のロジックにより 'secret' がリセットされていることを確認
75
76echo "\n";
77
78// 4. 古いタイプの「wakeup バイパス」の試行 (プロパティ数を少なく偽装)
79// (注意: PHP 7.0以降では、この手法では __wakeup はスキップされず、依然として呼び出されます。)
80echo "Attempting old-style 'wakeup bypass' (fewer properties, mostly patched in PHP 7+):\n";
81$oldBypassData = 'O:17:"App\\VulnerableObject":1:{s:4:"data";s:16:"sensitive_info";}';
82echo "Old Bypass Data: " . $oldBypassData . "\n";
83$oldBypassObject = unserialize($oldBypassData); // __wakeup は依然として呼び出されるはず
84if ($oldBypassObject instanceof VulnerableObject) {
85    $oldBypassObject->showSecret(); // secret プロパティはデフォルト値か、__wakeupでリセットされる
86} else {
87    echo "Deserialization failed or resulted in unexpected type.\n";
88}
89
90?>

PHPの__wakeupメソッドは、オブジェクトがデシリアライズ(文字列形式から元のオブジェクト形式に戻される)された直後に自動的に呼び出される特殊なマジックメソッドです。このメソッドは引数を取らず、戻り値もありません(void)。

主な役割は、デシリアライズされたオブジェクトの状態が正しいか検証すること、またはデータベース接続などのリソースを再初期化することです。例えば、外部から受け取ったシリアライズデータが悪意を持って改ざんされ、オブジェクト内の重要なプロパティが不正な値に変更されてしまった場合、__wakeupメソッド内でその改ざんを検知し、安全な初期値に戻すことで、オブジェクトの整合性を保ち、セキュリティ上のリスクを軽減できます。

以前のPHPバージョンでは、シリアライズデータ内のプロパティ数を偽装することで__wakeupメソッドの呼び出しをスキップさせる「wakeupバイパス」と呼ばれる手法が存在しました。しかし、PHP 7.0以降ではこの手法は機能せず、__wakeupメソッドは常に呼び出されるようになっています。これにより、デシリアライズ時のセキュリティチェックがより確実に実行されるようになりました。

したがって、__wakeupメソッドは、オブジェクトの安全な利用とデシリアライズ時の整合性確保のために、現在でも非常に重要な役割を担っています。

このサンプルコードの__wakeupメソッドは、unserialize()関数でオブジェクトが復元された直後に自動的に実行されます。PHP 7.0以降では、以前のバージョンで可能だったプロパティ数を偽装して__wakeupの実行をスキップする「wakeupバイパス」は機能しません。そのため、デシリアライズ時にオブジェクトの内部状態が不正に改ざんされていないかを確認し、安全な状態にリセットするセキュリティチェックの役割が非常に重要です。外部からの入力によるオブジェクトインジェクション攻撃を防ぐため、安全な値の強制やリソースの再初期化処理を記述することが推奨されます。

関連コンテンツ

関連プログラミング言語