[PHP]__set_state()とは?var_export()から生成されたコードでオブジェクトを復元する方法を徹底解説

PHP

はじめに

以前の記事で、変数をPHPコードとして評価可能な文字列に変換する var_export() 関数を解説した際、オブジェクトを出力すると \ClassName::__set_state(array(...)) という見慣れない形式になることに触れました。今回取り上げる __set_state() は、まさにこの var_export() が生成するコードを実際に実行した際に呼び出される、特殊な静的マジックメソッドです。

__set_state() は、var_export() によってエクスポートされたオブジェクトのコード表現を、eval() や設定ファイルの include によって再び実際のオブジェクトとして復元するための仕組みです。日常的に意識することは少ないマジックメソッドですが、設定キャッシュの仕組みや、テストフィクスチャの自動生成といった場面で、その役割を理解しておくことが役立ちます。本記事では、基本的な仕組みから実践的な活用例まで詳しく解説します。


メソッド概要

項目内容
メソッド名__set_state()
種別マジックメソッド(静的メソッドとして定義する必要がある)
シグネチャpublic static function __set_state(array $properties): object
引数$properties — プロパティ名と値の連想配列
呼び出しタイミングvar_export() が出力したコードが実際に評価(evalやinclude)された時
戻り値復元されたオブジェクトのインスタンス
対応バージョンPHP 5.1.0以降
関連する関数var_export()(このメソッドが処理するコードを生成する側)

var_export()との連携の流れ(イメージ図)

  $config = new AppConfig('MyApp', true);
              │
              ▼
        var_export($config, true)
              │
              ▼
  出力されるPHPコード文字列:
  "\AppConfig::__set_state(array(
     'name' => 'MyApp',
     'debug' => true,
  ))"
              │
              ▼ (このコード文字列をファイルに保存し、後でincludeする)
              │
  include '/path/to/exported_config.php';
              │
              ▼
  ┌───────────────────────────┐
  │ PHPが \AppConfig::__set_state(...)   │
  │ という呼び出しを実行しようとする         │
  └───────────┬───────────────┘
              ▼
  __set_state(['name' => 'MyApp', 'debug' => true])
  ★この記事の対象。ここでオブジェクトを再構築して返す

ポイントは、__set_state() が**var_export() というエクスポート処理と対をなす、インポート(復元)処理**を担うという点です。この仕組みを理解しないままオブジェクトを含むデータを var_export() して include すると、「__set_state()が実装されていない」という致命的エラーに遭遇することがあります。


実践サンプル7選

例1:基本的な使い方

<?php

class AppConfig
{
    public function __construct(
        public string $name,
        public bool $debug
    ) {
    }

    /**
     * var_export()が生成したコードから
     * 自分自身を再構築する静的メソッド
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['name'], $properties['debug']);
    }
}

$original = new AppConfig('MyApp', true);
$exported = var_export($original, true);
echo $exported . PHP_EOL;

// エクスポートされたコードを実際に評価して復元する
$restored = eval('return ' . $exported . ';');
echo "{$restored->name} (debug: " . ($restored->debug ? 'true' : 'false') . ')' . PHP_EOL;

例2:__set_state()を未実装のクラスで起きるエラーを確認する

<?php

class UnimplementedConfig
{
    public function __construct(public string $value)
    {
    }
    // __set_state()を実装していない
}

$obj = new UnimplementedConfig('サンプル');
$exported = var_export($obj, true);
echo $exported . PHP_EOL;
// 出力例: \UnimplementedConfig::__set_state(array('value' => 'サンプル'))

try {
    eval('return ' . $exported . ';');
} catch (Error $e) {
    // __set_state()が存在しないため、Errorが発生する
    echo 'エラー: ' . $e->getMessage() . PHP_EOL;
}

例3:設定キャッシュファイルの生成と読み込みへの応用

<?php

class DatabaseConfig
{
    public function __construct(
        public string $host,
        public int $port,
        public string $database
    ) {
    }

    public static function __set_state(array $properties): self
    {
        return new self(
            $properties['host'],
            $properties['port'],
            $properties['database']
        );
    }
}

class ConfigCacheWriter
{
    /**
     * オブジェクトをvar_export()でキャッシュファイルとして保存し、
     * 次回以降はinclude経由で高速に復元する
     */
    public function cache(object $config, string $path): void
    {
        $code = "<?php\n\nreturn " . var_export($config, true) . ";\n";
        file_put_contents($path, $code);
    }

    public function load(string $path): mixed
    {
        // includeによってファイルが評価され、__set_state()が自動的に呼ばれる
        return include $path;
    }
}

$writer = new ConfigCacheWriter();
$config = new DatabaseConfig('localhost', 5432, 'myapp_db');
$writer->cache($config, '/tmp/db_config_cache.php');

$restored = $writer->load('/tmp/db_config_cache.php');
echo "{$restored->host}:{$restored->port}/{$restored->database}" . PHP_EOL;

例4:プロパティ名のタイプミスを検知する堅牢な実装

<?php

class StrictPoint
{
    public function __construct(
        public float $x,
        public float $y
    ) {
    }

    /**
     * 想定外のキーが含まれていないかを検証してから復元する
     */
    public static function __set_state(array $properties): self
    {
        if (!isset($properties['x'], $properties['y'])) {
            throw new InvalidArgumentException('x, yプロパティが不足しています');
        }

        return new self((float) $properties['x'], (float) $properties['y']);
    }
}

$point = new StrictPoint(3.5, 7.2);
$exported = var_export($point, true);
$restored = eval('return ' . $exported . ';');
echo "({$restored->x}, {$restored->y})" . PHP_EOL;

例5:readonlyプロパティを持つクラスでの実装パターン

<?php

final class ImmutablePoint
{
    public function __construct(
        public readonly float $x,
        public readonly float $y
    ) {
    }

    /**
     * readonlyプロパティであっても、
     * コンストラクタ経由で新しいインスタンスとして再構築すれば問題ない
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['x'], $properties['y']);
    }
}

$point = new ImmutablePoint(1.0, 2.0);
$exported = var_export($point, true);
$restored = eval('return ' . $exported . ';');
echo "({$restored->x}, {$restored->y})" . PHP_EOL;

例6:ネストしたオブジェクトを含む複雑な構造の復元

<?php

class Address
{
    public function __construct(public string $city, public string $zip)
    {
    }

    public static function __set_state(array $properties): self
    {
        return new self($properties['city'], $properties['zip']);
    }
}

class Customer
{
    public function __construct(public string $name, public Address $address)
    {
    }

    /**
     * ネストしたオブジェクト(Address)も、
     * var_export()によって再帰的に__set_state()呼び出しとして出力される
     */
    public static function __set_state(array $properties): self
    {
        return new self($properties['name'], $properties['address']);
    }
}

$customer = new Customer('太郎', new Address('東京', '100-0001'));
$exported = var_export($customer, true);
echo $exported . PHP_EOL;

$restored = eval('return ' . $exported . ';');
echo "{$restored->name} - {$restored->address->city}" . PHP_EOL;

例7:テストフィクスチャ生成ツールでの活用

<?php

class TestFixture
{
    public function __construct(
        public int $id,
        public string $status
    ) {
    }

    public static function __set_state(array $properties): self
    {
        return new self($properties['id'], $properties['status']);
    }
}

class FixtureFileGenerator
{
    /**
     * テスト用のオブジェクトを、
     * そのままPHPコードとしてテストファイルに埋め込めるようにする
     */
    public function generateFixtureCode(object $fixture, string $variableName): string
    {
        return "\${$variableName} = " . var_export($fixture, true) . ";";
    }
}

$generator = new FixtureFileGenerator();
$fixture = new TestFixture(1, 'active');
echo $generator->generateFixtureCode($fixture, 'expectedFixture') . PHP_EOL;
// 生成されたコードをテストファイルに貼り付けて使用する

関連機能との比較

機能役割__set_state()との違い
__set_state()var_export()が出力したコードからオブジェクトを再構築する本記事の対象。静的メソッドとして定義する
var_export()変数をPHPコードとして評価可能な文字列に変換する__set_state()が処理する対象のコードを生成する側
__unserialize()unserialize()によるオブジェクト復元を制御するシリアライズ文字列からの復元であり、PHPコードの評価ではない
__construct()通常のオブジェクト生成時に呼ばれる__set_state()は静的メソッドで、内部からnew self(...)のように呼び出すのが一般的
JSON + json_decode()JSON形式でのデータのシリアライズ/デシリアライズvar_export()/__set_state()とは異なる、別のデータ永続化アプローチ

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

  1. __set_state()を実装していないと復元時にエラーになる var_export() 自体はオブジェクトのプロパティを問題なく出力できますが、そのコードを実際に eval() や include で評価しようとした際、対象クラスに __set_state() が実装されていないと致命的なエラーが発生します(例2を参照)。
  2. 静的メソッドとして定義する必要がある __set_state() は必ず public static function として定義します。通常のインスタンスメソッドとして定義してしまうと正しく機能しません。
  3. privateプロパティを持つクラスでは出力自体に制約がある var_export() は、private/protectedプロパティを持つオブジェクトを正しくエクスポートできない場合があります。__set_state() を活用する設計であれば、基本的にpublicプロパティ(またはコンストラクタプロモーションされたpublicプロパティ)を中心としたシンプルな構造にしておくのが安全です。
  4. 信頼できない入力から生成されたコードを評価しない __set_state() の仕組みは、最終的に eval() や include によってPHPコードそのものを実行します。外部から受け取った、あるいはユーザー入力を含む可能性のあるデータをこの方法で処理することは、コード実行のリスクを伴うため避けるべきです。設定キャッシュなど、自分自身が生成したコードのみを対象にしましょう。
  5. Composerのオートロードとの兼ね合いに注意する var_export() が出力するクラス名には先頭に \ が付与されます(例: \AppConfig::__set_state(...))。名前空間を使っているプロジェクトでは、クラスが正しくオートロードされる状態になっているかを確認しておく必要があります。

まとめ

観点まとめ
何をするメソッドかvar_export()が出力したオブジェクトのコード表現を評価した際に、実際のオブジェクトとして再構築する
主な用途設定キャッシュファイルの読み込み、テストフィクスチャの復元
定義の必須要件public static functionとして定義する必要がある
対応する関数var_export()(エクスポート側)との組み合わせで機能する
注意点未実装時のエラー、privateプロパティの扱い、信頼できないコード評価の回避

__set_state() は、var_export() と対をなす、やや特殊な静的マジックメソッドです。日常的に目にする機会は少ないものの、設定データのPHPコードとしてのキャッシュなど、パフォーマンスを重視した永続化の仕組みを実装する際には、その役割を正しく理解しておくことが重要です。

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