[PHP]__wakeup()とは?復元後のオブジェクトを初期化する旧来の仕組みを徹底解説

PHP

はじめに

前回の記事では、シリアライズ対象のプロパティ名を指定する古い仕組み __sleep() を解説しました。今回取り上げる __wakeup() は、その対となる、unserialize() によってオブジェクトが復元された直後に呼び出されるマジックメソッドです。

__wakeup() は、PHP 7.4で __unserialize() が登場する以前から存在する、より古い復元時フックの仕組みです。__unserialize() が「プロパティの復元処理そのもの」を完全に制御するのに対し、__wakeup() は「PHPがデフォルトの方法でプロパティを復元した後に、追加の初期化処理を行う」という、より限定的な役割を担います。データベース接続の再確立や、一時的なリソースの初期化など、復元直後に必要な後処理を実装する際に使われます。本記事では、その役割と __unserialize() との違い、実践的な活用例を詳しく解説します。


メソッド概要

項目内容
メソッド名__wakeup()
種別マジックメソッド
シグネチャpublic function __wakeup(): void
引数なし
呼び出しタイミングunserialize() によってオブジェクトが復元された直後
戻り値なし(void)
対応バージョンPHP 4以降(__unserialize()登場以前からの仕組み)
対になるメソッド__sleep()(シリアライズ時に呼ばれる)
PHP 7.4以降の位置づけ__unserialize()が定義されていればそちらが優先される

__wakeup()と__unserialize()の役割の違い(イメージ図)

  【__wakeup() - 古い仕組み】
  unserialize($string)
        │
        ▼
  1. PHPがデフォルトの方法で
     すべてのプロパティを自動的に復元する
        │
        ▼
  2. __wakeup() が呼ばれる
     ← プロパティは既に復元済み。
       ここでは「追加の後処理」だけを行う
     ★この記事の対象


  【__unserialize() - PHP 7.4以降の新しい仕組み】
  unserialize($string)
        │
        ▼
  __unserialize($data) が呼ばれる
     ← プロパティの復元処理そのものを
       ここで完全にコントロールする

ポイントは、__wakeup() が実行される時点ですでにプロパティの復元自体は完了しているという点です。__wakeup() にできるのは、その後の「後片付け」や「追加の初期化」にとどまり、復元されるデータの内容自体を変更することは(直接的には)想定されていません。


実践サンプル7選

例1:基本的な使い方

<?php

class BasicWakeupDemo
{
    public function __construct(private string $name)
    {
        echo 'コンストラクタが呼ばれました' . PHP_EOL;
    }

    /**
     * 復元直後に呼ばれ、プロパティは既に自動的に設定済み
     */
    public function __wakeup(): void
    {
        echo "__wakeup()が呼ばれました。名前: {$this->name}" . PHP_EOL;
    }
}

$original = new BasicWakeupDemo('太郎'); // コンストラクタが呼ばれる
$serialized = serialize($original);
$restored = unserialize($serialized);    // __wakeup()が呼ばれる(コンストラクタは呼ばれない)

例2:データベース接続を復元後に再確立するクラス

<?php

class LegacyDatabaseModel
{
    private array $config;
    private $connection;

    public function __construct(array $config)
    {
        $this->config = $config;
        $this->connection = $this->connect();
    }

    private function connect()
    {
        echo '接続を確立しました' . PHP_EOL;
        return 'resource_handle'; // 実際にはPDO等のリソースを想定
    }

    public function __sleep(): array
    {
        return ['config']; // connectionはリソースのため除外
    }

    /**
     * 復元後、configを元に接続を再確立する
     */
    public function __wakeup(): void
    {
        $this->connection = $this->connect();
    }
}

$model = new LegacyDatabaseModel(['host' => 'localhost']);
$restored = unserialize(serialize($model));

例3:復元時にタイムスタンプを記録する監査ログパターン

<?php

class AuditedRestoration
{
    private string $data;
    private ?string $restoredAt = null;

    public function __construct(string $data)
    {
        $this->data = $data;
    }

    public function __sleep(): array
    {
        return ['data'];
    }

    /**
     * 復元が行われたタイミングを記録する、
     * 監査ログ的な用途での活用例
     */
    public function __wakeup(): void
    {
        $this->restoredAt = date('Y-m-d H:i:s');
        error_log("オブジェクトが復元されました: {$this->restoredAt}");
    }

    public function getRestoredAt(): ?string
    {
        return $this->restoredAt;
    }
}

$obj = new AuditedRestoration('サンプルデータ');
$restored = unserialize(serialize($obj));
echo $restored->getRestoredAt() . PHP_EOL;

例4:復元後のデータ整合性を検証するクラス

<?php

class IntegrityCheckedModel
{
    public function __construct(private int $id, private string $checksum = '')
    {
        $this->checksum = $this->calculateChecksum();
    }

    private function calculateChecksum(): string
    {
        return md5((string) $this->id);
    }

    public function __sleep(): array
    {
        return ['id', 'checksum'];
    }

    /**
     * 復元後、チェックサムを再計算して
     * データが改ざんされていないかを検証する
     */
    public function __wakeup(): void
    {
        if ($this->calculateChecksum() !== $this->checksum) {
            throw new UnexpectedValueException('データの整合性チェックに失敗しました');
        }
    }
}

$model = new IntegrityCheckedModel(42);
$restored = unserialize(serialize($model));
echo '整合性チェックに成功しました' . PHP_EOL;

例5:__unserialize()への移行パターンを示す比較

<?php

class LegacyWakeupStyle
{
    private $resource;

    public function __construct(private string $name)
    {
        $this->resource = 'initialized';
    }

    public function __sleep(): array
    {
        return ['name'];
    }

    public function __wakeup(): void
    {
        // プロパティは既に復元済みなので、追加の初期化だけを行う
        $this->resource = 'reinitialized';
    }
}

class ModernUnserializeStyle
{
    private $resource;

    public function __construct(private string $name)
    {
        $this->resource = 'initialized';
    }

    public function __serialize(): array
    {
        return ['name' => $this->name];
    }

    /**
     * __unserialize()であれば、プロパティ設定と
     * 追加の初期化処理を1箇所で完結できる
     */
    public function __unserialize(array $data): void
    {
        $this->name = $data['name'];
        $this->resource = 'reinitialized';
    }
}

$legacy = unserialize(serialize(new LegacyWakeupStyle('太郎')));
$modern = unserialize(serialize(new ModernUnserializeStyle('太郎')));
echo '両方とも正常に復元されました' . PHP_EOL;

例6:両方の仕組みが共存する場合の優先順位を確認するデモ

<?php

class BothMechanismsCoexist
{
    public function __construct(private string $value)
    {
    }

    public function __serialize(): array
    {
        return ['value' => $this->value];
    }

    public function __unserialize(array $data): void
    {
        echo '__unserialize()が呼ばれました(こちらが優先される)' . PHP_EOL;
        $this->value = $data['value'];
    }

    /**
     * PHP 7.4以降、__unserialize()が定義されている場合、
     * __wakeup()は呼び出されない
     */
    public function __wakeup(): void
    {
        echo '__wakeup()が呼ばれました(実際には呼ばれない)' . PHP_EOL;
    }
}

$obj = new BothMechanismsCoexist('テスト');
unserialize(serialize($obj)); // "__unserialize()が呼ばれました"のみ出力される

例7:継承関係における復元後処理の連携

<?php

class BaseResource
{
    protected $handle;

    protected function reinitializeHandle(): void
    {
        $this->handle = 'base_handle';
    }
}

class ExtendedResource extends BaseResource
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
        $this->handle = 'initial_handle';
    }

    public function __sleep(): array
    {
        return ['name'];
    }

    /**
     * 親クラスの後処理メソッドを呼び出しつつ、
     * 子クラス固有の追加処理も行う
     */
    public function __wakeup(): void
    {
        $this->reinitializeHandle();
        echo "{$this->name}のリソースが再初期化されました" . PHP_EOL;
    }
}

$resource = new ExtendedResource('サンプル');
unserialize(serialize($resource));

関連機能との比較

機能役割__wakeup()との違い
__wakeup()復元直後に追加の初期化処理を行う本記事の対象。プロパティ自体は既に復元済みの状態で呼ばれる
__sleep()シリアライズ対象のプロパティ名を指定する__wakeup()と対になる、シリアライズ時の仕組み(前回記事を参照)
__unserialize()プロパティの復元処理そのものを制御するPHP 7.4以降の新しい仕組みで、復元ロジックをより柔軟に制御できる
__serialize()シリアライズするデータを自由形式の配列で定義__unserialize()と対になる、新しいシリアライズ側の仕組み
__construct()通常のオブジェクト生成時に呼ばれるunserialize()による復元時には呼ばれない点が共通の注意点

よくある落とし穴(注意点)

  1. 新規開発では__unserialize()を優先すべき __wakeup() はレガシーな仕組みであり、PHP 7.4以降では __unserialize() の使用が推奨されています。特別な互換性要件がない限り、新しい仕組みを選びましょう。
  2. __unserialize()と共存すると呼び出されない 同じクラスに __wakeup() と __unserialize() の両方が定義されている場合、PHP 7.4以降では __unserialize() が優先され、__wakeup() は呼び出されません(例6を参照)。
  3. コンストラクタが呼ばれないことを前提に設計する __wakeup() が呼ばれる際も、__unserialize() の場合と同様に、通常の __construct() は実行されません。コンストラクタでしか設定していない重要な処理があれば、__wakeup() 内でも改めて行う必要があります。
  4. 信頼できない入力からの復元時のリスクに注意する __wakeup() 内で外部リソースへの接続やファイル操作を行う場合、復元元のデータが信頼できないものだと、意図しない処理が実行されるリスクがあります。これは unserialize() 全般に関わるセキュリティ上の重要な注意点です。
  5. PHPのバージョンによる既知の脆弱性にも留意する 過去のPHPバージョンでは、__wakeup() を悪用した脆弱性(CVEとして報告されたケースを含む)も存在しました。信頼できないデータを unserialize() する設計自体を避け、JSON形式など、より安全な代替手段を優先することが推奨されます。

まとめ

観点まとめ
何をするメソッドかunserialize()によってプロパティが復元された直後に、追加の初期化処理を行う
主な用途(レガシーコードにおける)リソースの再接続、復元後の整合性チェック、監査ログの記録
対になるメソッド__sleep()(シリアライズ時のプロパティ名指定)
現在の位置づけPHP 7.4以降は__unserialize()への移行が推奨される
注意点プロパティは既に復元済みの状態で呼ばれること、新しい仕組みとの共存時は呼ばれないこと

__wakeup() は、PHPの復元処理における歴史的な後処理フックです。__sleep() と対で使われ、復元直後のリソース再接続のような限定的な用途に活用されてきました。既存のレガシーコードではその役割を理解しておく必要がありますが、新規開発ではより柔軟な __unserialize() への移行を検討し、unserialize() 全般に伴うセキュリティリスクにも十分注意を払いましょう。

タイトルとURLをコピーしました