はじめに
以前の記事で、変数を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()とは異なる、別のデータ永続化アプローチ |
よくある落とし穴(注意点)
__set_state()を実装していないと復元時にエラーになるvar_export()自体はオブジェクトのプロパティを問題なく出力できますが、そのコードを実際にeval()やincludeで評価しようとした際、対象クラスに__set_state()が実装されていないと致命的なエラーが発生します(例2を参照)。- 静的メソッドとして定義する必要がある
__set_state()は必ずpublic static functionとして定義します。通常のインスタンスメソッドとして定義してしまうと正しく機能しません。 - privateプロパティを持つクラスでは出力自体に制約がある
var_export()は、private/protectedプロパティを持つオブジェクトを正しくエクスポートできない場合があります。__set_state()を活用する設計であれば、基本的にpublicプロパティ(またはコンストラクタプロモーションされたpublicプロパティ)を中心としたシンプルな構造にしておくのが安全です。 - 信頼できない入力から生成されたコードを評価しない
__set_state()の仕組みは、最終的にeval()やincludeによってPHPコードそのものを実行します。外部から受け取った、あるいはユーザー入力を含む可能性のあるデータをこの方法で処理することは、コード実行のリスクを伴うため避けるべきです。設定キャッシュなど、自分自身が生成したコードのみを対象にしましょう。 - Composerのオートロードとの兼ね合いに注意する
var_export()が出力するクラス名には先頭に\が付与されます(例:\AppConfig::__set_state(...))。名前空間を使っているプロジェクトでは、クラスが正しくオートロードされる状態になっているかを確認しておく必要があります。
まとめ
| 観点 | まとめ |
|---|---|
| 何をするメソッドか | var_export()が出力したオブジェクトのコード表現を評価した際に、実際のオブジェクトとして再構築する |
| 主な用途 | 設定キャッシュファイルの読み込み、テストフィクスチャの復元 |
| 定義の必須要件 | public static functionとして定義する必要がある |
| 対応する関数 | var_export()(エクスポート側)との組み合わせで機能する |
| 注意点 | 未実装時のエラー、privateプロパティの扱い、信頼できないコード評価の回避 |
__set_state() は、var_export() と対をなす、やや特殊な静的マジックメソッドです。日常的に目にする機会は少ないものの、設定データのPHPコードとしてのキャッシュなど、パフォーマンスを重視した永続化の仕組みを実装する際には、その役割を正しく理解しておくことが重要です。
