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

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

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

作成日: 更新日:

基本的な使い方

__wakeupメソッドは、unserialize()関数によってオブジェクトがデシリアライズ(直列化復元)される際に、自動的に呼び出されるマジックメソッドです。このメソッドは、シリアル化されたデータからオブジェクトを再構築する過程で、必要な初期化処理やリソースの再接続などを行うために設計されています。InvalidArgumentExceptionクラスは、PHPの組み込み例外クラスであるExceptionを継承しており、__wakeupメソッドも親クラスから引き継いでいます。しかし、セキュリティ上の理由から、例外オブジェクトのデシリアライズは非推奨とされています。そのため、PHP 8.0.0以降、InvalidArgumentExceptionを含むExceptionのサブクラスのオブジェクトをunserialize()しようとすると、この__wakeupメソッドが呼び出され、例外をスローして処理を失敗させます。この仕組みにより、不正なシリアル化データから意図しない例外オブジェクトが生成されることを防いでいます。したがって、このメソッドは開発者が直接呼び出すことを意図したものではなく、PHPのシリアライゼーション機構によって内部的に利用されるものです。

構文(syntax)

1<?php
2
3try {
4    $serializedObject = serialize(new InvalidArgumentException());
5    unserialize($serializedObject);
6} catch (Exception $e) {
7    // As of PHP 8.0, unserializing an InvalidArgumentException is not allowed and throws an Exception.
8    // The __wakeup() method is called internally by unserialize() to enforce this.
9}

引数(parameters)

引数なし

引数はありません

戻り値(return)

戻り値なし

戻り値はありません

サンプルコード

__wakeup bypass 脆弱性とその対策

1<?php
2
3/**
4 * __wakeup()マジックメソッドの動作を説明するためのクラスです。
5 * このメソッドは、オブジェクトがunserialize()によって復元される際に自動的に呼び出されます。
6 */
7class WakeupDemo
8{
9    // オブジェクトの状態を保持するプロパティ
10    public string $status;
11
12    /**
13     * オブジェクトの生成時に状態を初期化します。
14     */
15    public function __construct()
16    {
17        $this->status = 'created';
18        echo "オブジェクトが生成されました。 Status: {$this->status}\n";
19    }
20
21    /**
22     * unserialize()時に自動的に呼び出されるマジックメソッドです。
23     * ここでオブジェクトの再初期化や検証処理を行うことができます。
24     */
25    public function __wakeup(): void
26    {
27        $this->status = 're-initialized by __wakeup';
28        echo "__wakeup() が呼び出されました。\n";
29    }
30}
31
32// クラスのインスタンスを生成
33$obj = new WakeupDemo();
34
35// オブジェクトをシリアライズ(文字列に変換)
36$serialized = serialize($obj);
37echo "シリアライズされたデータ: {$serialized}\n\n";
38
39/*
40 * 【__wakeup bypass 脆弱性について】
41 * かつてのPHPバージョンには、シリアライズされた文字列を不正に改変し、
42 * オブジェクトのプロパティ数を本来より多く見せかけることで、
43 * __wakeup() の呼び出しを回避(バイパス)できてしまう脆弱性がありました (CVE-2016-7124)。
44 *
45 * この脆弱性を利用されると、__wakeup() 内で想定されていたセキュリティチェックや
46 * 初期化処理が実行されないままオブジェクトが復元されてしまう危険性がありました。
47 *
48 * 現在のPHP 8ではこの脆弱性は修正されているため、__wakeup() は正しく呼び出されます。
49 */
50
51echo "シリアライズされたデータからオブジェクトを復元します...\n";
52// 文字列からオブジェクトを復元(デシリアライズ)
53// この処理の過程で __wakeup() メソッドが自動的に実行されます。
54$unserializedObj = unserialize($serialized);
55
56// __wakeup() によって状態が変更されたことを確認
57echo "復元されたオブジェクトの Status: {$unserializedObj->status}\n";
58

このPHPコードは、オブジェクトが復元される際に自動的に呼び出される__wakeup()マジックメソッドの動作を解説するものです。このメソッドは引数を取らず、戻り値もありません。主な役割は、オブジェクトが再度利用可能になるための初期化処理を行うことです。

サンプルコードでは、まずstatusプロパティを持つWakeupDemoオブジェクトを生成し、serialize()関数で文字列データに変換します。この時点ではstatusの値は 'created' です。

次に、unserialize()関数を使って、この文字列データからオブジェクトを復元します。この復元の過程で__wakeup()メソッドが自動的に実行され、内部の処理によってstatusプロパティの値が 're-initialized by __wakeup' に書き換えられます。結果として、復元されたオブジェクトのstatusは、生成時とは異なる値に更新されていることが確認できます。

なお、かつてのPHPには__wakeup()の呼び出しを回避できる脆弱性がありましたが、サンプルコードが対象とするPHP 8ではこの問題は修正されているため、unserialize()実行時に__wakeup()は必ず呼び出されます。

__wakeup()は、unserialize()関数でオブジェクトを復元する際に自動で呼び出される特別なメソッドです。最も重要な注意点は、ユーザー入力など信頼できない外部のデータを安易にunserialize()しないことです。悪意のある文字列を渡されると、意図しないオブジェクトが生成され、深刻なセキュリティ脆弱性につながる危険性があります。サンプルコードで言及されている__wakeup()の呼び出しを回避する脆弱性は古いバージョンの問題であり、現在のPHPでは対策済みです。安全性を高めるため、オブジェクトの保存やデータ交換には、より安全なJSON形式などを用いることが一般的に推奨されます。

PHP __wakeup メソッドで接続を復元する

1<?php
2
3declare(strict_types=1);
4
5/**
6 * データベース接続をシミュレートするクラス
7 * __wakeup マジックメソッドの動作を示します。
8 */
9class DatabaseConnection
10{
11    private string $host;
12    private ?string $connectionId = null;
13
14    public function __construct(string $host)
15    {
16        $this->host = $host;
17        $this->connect();
18    }
19
20    /**
21     * __wakeup() マジックメソッド
22     *
23     * unserialize() によってオブジェクトが復元される際に自動的に呼び出されます。
24     * シリアライズによって失われたリソース(例: データベース接続)を
25     * 再確立するなどの初期化処理に利用されます。
26     */
27    public function __wakeup(): void
28    {
29        echo "--- __wakeup() が呼び出されました ---\n";
30        // 接続を再確立する
31        $this->connect();
32    }
33
34    /**
35     * 接続処理をシミュレートします。
36     */
37    public function connect(): void
38    {
39        // 実際にはデータベースに接続する処理が入ります
40        $this->connectionId = "conn_" . bin2hex(random_bytes(4));
41        echo "データベースに接続しました。 (Host: {$this->host}, ID: {$this->connectionId})\n";
42    }
43
44    public function getConnectionId(): ?string
45    {
46        return $this->connectionId;
47    }
48}
49
50// 1. クラスのインスタンスを作成します
51echo "1. インスタンスを作成します。\n";
52$db = new DatabaseConnection('localhost');
53echo "\n";
54
55// 2. インスタンスをシリアライズ(文字列に変換)します
56//    これにより、オブジェクトの状態を保存したり転送したりできます。
57echo "2. インスタンスをシリアライズします。\n";
58$serializedDb = serialize($db);
59echo "シリアライズされたデータ: " . $serializedDb . "\n";
60echo "\n";
61
62// 3. シリアライズされた文字列からインスタンスを復元(デシリアライズ)します
63//    この処理の過程で、__wakeup() メソッドが自動的に呼び出されます。
64echo "3. インスタンスをデシリアライズして復元します。\n";
65$restoredDb = unserialize($serializedDb);
66echo "\n";
67
68// 4. 復元されたオブジェクトの状態を確認します
69//    __wakeup() によって接続が再確立されていることがわかります。
70echo "4. 復元されたオブジェクトの接続IDを確認します。\n";
71echo "復元後の接続ID: " . $restoredDb->getConnectionId() . "\n";
72

PHPの__wakeupは、オブジェクトのデシリアライズ時に自動的に呼び出される「マジックメソッド」です。このメソッドには引数はなく、戻り値もありません。

オブジェクトをserialize()関数で文字列に変換(シリアライズ)すると、ファイルに保存したりネットワークで送信したりできます。しかし、データベース接続のような外部リソースへの接続情報は、この文字列化の過程で失われてしまいます。

その後、unserialize()関数を使って文字列からオブジェクトを復元(デシリアライズ)する際に、__wakeupメソッドが自動的に実行されます。このメソッドの主な役割は、シリアライズによって失われたリソースを再確立したり、オブジェクトが再び利用可能になるための準備処理を行ったりすることです。

サンプルコードでは、DatabaseConnectionオブジェクトを一度シリアライズし、その後デシリアライズしています。unserialize()が実行されたタイミングで__wakeupメソッドが呼び出され、内部でconnect()メソッドが再度実行されます。これにより、失われていたデータベース接続が再確立され、復元されたオブジェクトが正常に機能する状態に戻ります。このように__wakeupは、オブジェクトの「目覚め」の際に必要な初期化処理を定義するために利用されます。

__wakeupメソッドは、unserialize()関数でオブジェクトを復元する際に自動で呼び出される初期化用のメソッドです。インスタンス生成時に呼ばれる__constructとは役割が異なります。シリアライズではデータベース接続のようなリソースは保存できないため、__wakeup内で再接続処理を記述するのが一般的な使い方です。最も注意すべき点はセキュリティです。信頼できない外部のデータをunserialize()すると、深刻な脆弱性に繋がる危険性があります。そのため、デシリアライズするデータは必ず信頼できるソースからのものに限定し、許可するクラスを明示的に指定するなど、安全対策を徹底する必要があります。

関連コンテンツ

関連プログラミング言語

【PHP8.x】InvalidArgumentException::__wakeup()メソッドの使い方 | いっしー@Webエンジニア