[PHP]__sleep()とは?旧来のシリアライズ対象プロパティ指定の仕組みを徹底解説

PHP

はじめに

これまでの記事で、PHP 7.4以降の洗練されたシリアライズ制御の仕組みである __serialize() と __unserialize() を解説してきました。今回取り上げる __sleep() は、それらが登場する以前から存在していた、より古いシリアライズ制御の仕組みです。

__sleep() は、serialize() が呼ばれる際に実行され、「シリアライズの対象とするプロパティ名」を配列として返すことで、どのデータを保存するかを制御します。__serialize() が「保存する値そのもの」を自由な形式で返せるのに対し、__sleep() はあくまで既存のプロパティのうち、どれを含めるかを「選択」する、より制約のある仕組みです。しかし、PHP 7.4より前のバージョンとの互換性が必要なコードや、既存のレガシーコードベースでは今でも見かけることがあります。本記事では、その役割と __serialize() との違い、使用上の注意点を詳しく解説します。


メソッド概要

項目内容
メソッド名__sleep()
種別マジックメソッド
シグネチャpublic function __sleep(): array
引数なし
呼び出しタイミングserialize() 関数がそのオブジェクトを文字列化する際
戻り値シリアライズ対象とするプロパティ名の配列
対応バージョンPHP 4以降(__serialize()登場以前からの仕組み)
対になるメソッド__wakeup()(復元時に呼ばれる)
PHP 7.4以降の位置づけ__serialize()/__unserialize()が優先される

__sleep()と__serialize()の根本的な違い(イメージ図)

  【__sleep() - 古い仕組み】
  public function __sleep(): array {
      return ['name', 'age'];  ← プロパティ"名"の配列を返すだけ
  }
              │
              ▼
  指定した名前のプロパティが、
  そのままの値でシリアライズされる
  (値を加工したり、キー名を変えたりはできない)


  【__serialize() - PHP 7.4以降の新しい仕組み】
  public function __serialize(): array {
      return ['custom_key' => strtoupper($this->name)];
      ← 値そのものを自由に加工・整形できる
  }

ポイントは、__sleep() が**「既存のプロパティ名をそのまま選ぶ」ことしかできないのに対し、__serialize() は「どんな値を、どんなキー名で保存するか」を完全に自由に定義できる**という点です。この柔軟性の差が、新しい仕組みへの移行が推奨される大きな理由です。


実践サンプル7選

例1:基本的な使い方

<?php

class BasicSleepDemo
{
    public function __construct(
        private string $name,
        private int $age,
        private string $temporaryToken = ''
    ) {
    }

    /**
     * シリアライズ対象とするプロパティ名を配列で返す
     * (temporaryTokenは含めず除外する)
     */
    public function __sleep(): array
    {
        return ['name', 'age'];
    }
}

$demo = new BasicSleepDemo('太郎', 30, 'temp-xyz');
$serialized = serialize($demo);
echo $serialized . PHP_EOL;
// temporaryTokenの値は含まれない

例2:__wakeup()と組み合わせた従来の典型的なパターン

<?php

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

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

    private function connect()
    {
        return 'connected_resource'; // 実際にはリソースを返す想定
    }

    public function __sleep(): array
    {
        // connectionはリソースのためシリアライズ対象から除外
        return ['config'];
    }

    /**
     * 復元後、__wakeup()で接続を再確立する
     * (__sleep()とペアで使われる典型的な組み合わせ)
     */
    public function __wakeup(): void
    {
        $this->connection = $this->connect();
    }
}

$obj = new LegacyConnectionAware(['host' => 'localhost']);
$restored = unserialize(serialize($obj));
echo 'リソースが再接続されました' . PHP_EOL;

例3:存在しないプロパティ名を指定した場合の警告を確認する

<?php

class TypoInSleep
{
    private string $name = '太郎';

    /**
     * 注意: "nmae"はタイプミスで、実在しないプロパティ名
     * このような指定は警告(Notice)の原因になる
     */
    public function __sleep(): array
    {
        return ['nmae']; // "name"のタイプミス
    }
}

$obj = new TypoInSleep();
// serialize()実行時に "serialize(): "nmae" returned as member variable..." という警告が発生する可能性がある
$serialized = @serialize($obj);
echo $serialized . PHP_EOL;

例4:条件によってシリアライズするプロパティを動的に変える

<?php

class ConditionalSleepDemo
{
    public function __construct(
        private string $publicData,
        private string $sensitiveData,
        private bool $includeSensitive = false
    ) {
    }

    /**
     * フラグの状態に応じて、
     * シリアライズ対象のプロパティリストを動的に変える
     */
    public function __sleep(): array
    {
        $fields = ['publicData'];

        if ($this->includeSensitive) {
            $fields[] = 'sensitiveData';
        }

        return $fields;
    }
}

$withoutSensitive = new ConditionalSleepDemo('公開データ', '機密データ', false);
echo serialize($withoutSensitive) . PHP_EOL;

$withSensitive = new ConditionalSleepDemo('公開データ', '機密データ', true);
echo serialize($withSensitive) . PHP_EOL;

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

<?php

class LegacyStyle
{
    public function __construct(private string $name, private string $secret)
    {
    }

    public function __sleep(): array
    {
        return ['name']; // secretのみ除外できる、キー名や値の変更は不可
    }
}

class ModernStyle
{
    public function __construct(private string $name, private string $secret)
    {
    }

    /**
     * __serialize()であれば、除外に加えて
     * 値のマスキングなど、より柔軟な制御が可能
     */
    public function __serialize(): array
    {
        return [
            'name'         => $this->name,
            'secretMasked' => substr($this->secret, 0, 2) . '***',
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->name = $data['name'];
        $this->secret = ''; // マスクされているため復元できない
    }
}

echo serialize(new LegacyStyle('太郎', 'sensitive123')) . PHP_EOL;
echo serialize(new ModernStyle('太郎', 'sensitive123')) . PHP_EOL;

例6:親クラスのプロパティも含める継承パターン

<?php

class BaseModel
{
    protected int $id = 0;
}

class LegacyExtendedModel extends BaseModel
{
    private string $name = '';

    /**
     * 親クラス由来のプロパティ名も、
     * 子クラスの__sleep()の中で明示的に列挙する必要がある
     */
    public function __sleep(): array
    {
        return ['id', 'name']; // 親クラスのidも含める
    }
}

$model = new LegacyExtendedModel();
echo serialize($model) . PHP_EOL;

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

<?php

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

    public function __sleep(): array
    {
        echo '__sleep()が呼ばれました(実際には使われない)' . PHP_EOL;
        return ['value'];
    }

    /**
     * PHP 7.4以降、同じクラスに両方定義されている場合、
     * __serialize()が優先され、__sleep()は無視される
     */
    public function __serialize(): array
    {
        echo '__serialize()が呼ばれました(こちらが優先される)' . PHP_EOL;
        return ['value' => $this->value];
    }
}

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

関連機能との比較

機能役割__sleep()との違い
__sleep()シリアライズ対象のプロパティ「名」を指定する本記事の対象。古い仕組みで、値の自由な加工はできない
__wakeup()復元時に、デフォルト復元後の追加初期化を行う__sleep()と対になる、より古い復元側の仕組み
__serialize()シリアライズするデータを自由形式の配列で定義PHP 7.4以降の新しい仕組みで、値の加工やキー名の変更が可能
__unserialize()シリアライズデータからオブジェクトを復元__serialize()と対になる、新しい復元側の仕組み
JsonSerializablejson_encode()時の出力を制御するシリアライズ形式がJSONである点が異なる別の仕組み

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

  1. 新規開発では__serialize()/__unserialize()を優先すべき __sleep()/__wakeup() はレガシーな仕組みであり、PHP 7.4以降では、より柔軟で明確な __serialize()/__unserialize() の使用が推奨されています。新規開発でこれから実装する場合、特別な互換性要件がない限り、新しい仕組みを選びましょう。
  2. 存在しないプロパティ名を指定すると警告が発生する __sleep() の戻り値の配列にタイプミスなどで実在しないプロパティ名を含めてしまうと、serialize() 実行時に警告が発生します(例3を参照)。
  3. 値の加工やキー名の変更ができない __sleep() はあくまで「どのプロパティを含めるか」の選択にとどまり、値をマスキングしたり、保存時のキー名を変えたりすることはできません。こうした柔軟性が必要な場合は __serialize() を使う必要があります(例5を参照)。
  4. __serialize()と共存すると無視される 同じクラスに __sleep() と __serialize() の両方が定義されている場合、PHP 7.4以降では __serialize() が優先され、__sleep() は呼び出されません(例7を参照)。移行を中途半端に行うとこの点で混乱しやすいので注意しましょう。
  5. 親クラスのプロパティを含める際は明示的な列挙が必要 継承関係がある場合、__sleep() は自動的に親クラスのプロパティを認識してくれるわけではなく、開発者が戻り値の配列に明示的にプロパティ名を含める必要があります(例6を参照)。

まとめ

観点まとめ
何をするメソッドかserialize()実行時に、シリアライズ対象とするプロパティ名の配列を返す
主な用途(レガシーコードにおける)特定プロパティの除外、リソースの除外
対になるメソッド__wakeup()(復元時の追加初期化処理)
現在の位置づけPHP 7.4以降は__serialize()/__unserialize()への移行が推奨される
注意点値の加工ができないという制約、タイプミスによる警告、新しい仕組みとの共存時の優先順位

__sleep() は、PHPのシリアライズ制御における歴史的な仕組みであり、「プロパティ名を選ぶ」というシンプルな機能にとどまります。既存のレガシーコードを保守する際にはその役割を理解しておく必要がありますが、新規開発においては、より柔軟で明確な __serialize()/__unserialize() への移行を検討することをおすすめします。

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