__set_state — var_export 可导出对象
概述
__set_state() 是 PHP 自带的魔术方法,由 var_export() 函数触发。当对一个对象调用 var_export() 时,PHP 会生成包含对该对象调用 __set_state() 的 PHP 代码,使得对象可以被导出为可执行的 PHP 表达式。
与 __debugInfo() 面向调试不同,__set_state() 的核心目的是代码级别的对象序列化——生成可以在其他地方直接 eval() 或写入文件的合法 PHP 代码。
基础概念
触发场景
var_export()导出对象时自动调用- 生成的代码格式为
ClassName::__set_state(array(...)) - 需要手动实现该方法,否则未定义的类会抛出致命错误
方法签名
php
<?php
declare(strict_types=1);
public static function __set_state(array $properties): static- 参数
$properties是一个关联数组,包含对象的所有 public 属性 - 返回值必须是该类的实例
var_export 的作用
var_export() 将变量输出为合法的 PHP 代码,支持以下用法:
php
<?php
declare(strict_types=1);
// 输出到浏览器
var_export($object);
// 返回字符串
$code = var_export($object, true);
// 写入文件
file_put_contents('cache.php', '<?php return ' . var_export($object, true) . ';');语法与代码
基本用法
php
<?php
declare(strict_types=1);
class Point
{
public float $x;
public float $y;
public function __construct(float $x, float $y)
{
$this->x = $x;
$this->y = $y;
}
public static function __set_state(array $properties): static
{
return new static($properties['x'], $properties['y']);
}
}
$point = new Point(3.14, 2.71);
var_export($point);输出结果:
php
\Point::__set_state(array(
'x' => 3.14,
'y' => 2.71,
))导出与恢复
php
<?php
declare(strict_types=1);
class Config
{
public string $appName;
public string $environment;
public int $maxConnections;
public array $allowedIps;
public function __construct(
string $appName,
string $environment,
int $maxConnections,
array $allowedIps
) {
$this->appName = $appName;
$this->environment = $environment;
$this->maxConnections = $maxConnections;
$this->allowedIps = $allowedIps;
}
public static function __set_state(array $properties): static
{
return new static(
$properties['appName'],
$properties['environment'],
$properties['maxConnections'],
$properties['allowedIps'],
);
}
}
// 导出配置到文件
$config = new Config('MyApp', 'production', 100, ['192.168.1.0/24']);
$exported = '<?php return ' . var_export($config, true) . ';';
file_put_contents('/tmp/config_cache.php', $exported);
// 从文件恢复配置
$restoredConfig = require '/tmp/config_cache.php';
var_dump($restoredConfig->appName); // string(5) "MyApp"
var_dump($restoredConfig->environment); // string(10) "production"var_export 输出格式对比
php
<?php
declare(strict_types=1);
class Simple
{
public int $id = 1;
public string $name = 'test';
}
$simple = new Simple();
// var_export 使用 __set_state
var_export($simple);
// 输出: \Simple::__set_state(array('id' => 1, 'name' => 'test',))对比 var_dump() 和 print_r():
php
<?php
declare(strict_types=1);
// var_dump: 仅供调试查看
var_dump($simple);
// object(Simple)#1 (2) { ["id"]=> int(1) ["name"]=> string(4) "test" }
// print_r: 仅供人工阅读
print_r($simple);
// Simple Object ( [id] => 1 [name] => test )
// var_export: 可作为 PHP 代码执行
var_export($simple);
// \Simple::__set_state(array( 'id' => 1, 'name' => 'test', ))处理 private 和 protected 属性
php
<?php
declare(strict_types=1);
class SecureUser
{
private string $username;
private string $passwordHash;
protected int $loginAttempts;
public string $displayName;
public function __construct(
string $username,
string $passwordHash,
string $displayName
) {
$this->username = $username;
$this->passwordHash = $passwordHash;
$this->loginAttempts = 0;
$this->displayName = $displayName;
}
public static function __set_state(array $properties): static
{
$instance = new static(
$properties['username'] ?? '',
$properties['passwordHash'] ?? '',
$properties['displayName'] ?? '',
);
// 手动恢复 protected/private 属性
// 注意:var_export 只能导出 public 属性
return $instance;
}
}注意
var_export() 只能导出对象的 public 属性。对于 private 和 protected 属性,需要在 __set_state() 中通过其他方式恢复(如从数据库、缓存或默认值)。
与匿名类的关系
php
<?php
declare(strict_types=1);
$obj = new class {
public int $value = 42;
};
// 匿名类无法实现 __set_state
// var_export 对匿名类会报错
// Fatal error: Anonymous class cannot implement __set_state()详细说明
__set_state 的工作流程
- 调用
var_export($object)或var_export($object, true) - PHP 检查对象类是否定义了
__set_state()方法 - 如果已定义,生成
ClassName::__set_state(array(...))格式的代码 - 如果未定义,对内部类(如
stdClass)会使用特殊处理;对用户自定义类则产生警告 - 生成的代码可以被
eval()执行或写入 PHP 文件
与其他导出方式的对比
| 方式 | 输出格式 | 可执行 | 用途 |
|---|---|---|---|
var_export() | PHP 代码 | 是 | 缓存、配置文件 |
var_dump() | 调试信息 | 否 | 调试 |
print_r() | 可读文本 | 否 | 调试 |
serialize() | 二进制/字符串 | 需反序列化 | 数据持久化 |
json_encode() | JSON 字符串 | 否 | API/跨语言 |
与 serialize 的关键区别
php
<?php
declare(strict_types=1);
class Data
{
public int $id;
public string $payload;
public function __construct(int $id, string $payload)
{
$this->id = $id;
$this->payload = $payload;
}
public static function __set_state(array $properties): static
{
return new static($properties['id'], $properties['payload']);
}
}
$data = new Data(100, 'hello world');
// var_export 生成 PHP 代码
$code = var_export($data, true);
// \Data::__set_state(array('id' => 100, 'payload' => 'hello world',))
// serialize 生成二进制序列化字符串
$serialized = serialize($data);
// O:4:"Data":2:{s:2:"id";i:100;s:7:"payload";s:11:"hello world";}实战示例
场景一:配置缓存系统
php
<?php
declare(strict_types=1);
class AppConfiguration
{
public string $dbHost;
public int $dbPort;
public string $dbName;
public string $dbUser;
public string $dbPassword;
public bool $debugMode;
public string $cacheDriver;
public int $cacheTtl;
public function __construct(array $settings = [])
{
$this->dbHost = $settings['dbHost'] ?? 'localhost';
$this->dbPort = $settings['dbPort'] ?? 3306;
$this->dbName = $settings['dbName'] ?? 'app';
$this->dbUser = $settings['dbUser'] ?? 'root';
$this->dbPassword = $settings['dbPassword'] ?? '';
$this->debugMode = $settings['debugMode'] ?? false;
$this->cacheDriver = $settings['cacheDriver'] ?? 'file';
$this->cacheTtl = $settings['cacheTtl'] ?? 3600;
}
public static function __set_state(array $properties): static
{
return new static($properties);
}
public static function loadFromCache(string $cacheFile): ?static
{
if (file_exists($cacheFile)) {
return require $cacheFile;
}
return null;
}
public function saveToCache(string $cacheFile): void
{
$code = '<?php return ' . var_export($this, true) . ';';
file_put_contents($cacheFile, $code);
}
}
// 从数据库或配置文件加载配置
$config = new AppConfiguration([
'dbHost' => 'db.production.com',
'dbPort' => 5432,
'dbName' => 'myapp_production',
'dbUser' => 'app_user',
'dbPassword' => 'secure_password',
'debugMode' => false,
'cacheDriver' => 'redis',
'cacheTtl' => 7200,
]);
// 保存到缓存
$config->saveToCache('/tmp/app_config.cache.php');
// 从缓存加载(快速启动)
$cachedConfig = AppConfiguration::loadFromCache('/tmp/app_config.cache.php');
var_dump($cachedConfig->dbHost); // string(20) "db.production.com"场景二:路由配置导出
php
<?php
declare(strict_types=1);
class RouteDefinition
{
public string $method;
public string $path;
public string $handler;
public array $middleware;
public function __construct(
string $method,
string $path,
string $handler,
array $middleware = []
) {
$this->method = $method;
$this->path = $path;
$this->handler = $handler;
$this->middleware = $middleware;
}
public static function __set_state(array $properties): static
{
return new static(
$properties['method'],
$properties['path'],
$properties['handler'],
$properties['middleware'],
);
}
}
$routes = [
new RouteDefinition('GET', '/', 'HomeController::index', ['auth']),
new RouteDefinition('GET', '/api/users', 'UserController::list', ['auth', 'admin']),
new RouteDefinition('POST', '/api/users', 'UserController::create', ['auth']),
];
// 导出路由到缓存文件
$routeCache = '<?php return ' . var_export($routes, true) . ';';
file_put_contents('/tmp/routes.cache.php', $routeCache);
// 恢复路由
$cachedRoutes = require '/tmp/routes.cache.php';场景三:构建 AST 节点的导出
php
<?php
declare(strict_types=1);
class AstNode
{
public string $type;
public string $value;
/** @var AstNode[] */
public array $children;
public function __construct(
string $type,
string $value = '',
array $children = []
) {
$this->type = $type;
$this->value = $value;
$this->children = $children;
}
public static function __set_state(array $properties): static
{
return new static(
$properties['type'],
$properties['value'] ?? '',
$properties['children'] ?? [],
);
}
}
$ast = new AstNode('Program', '', [
new AstNode('Assignment', 'x = 10'),
new AstNode('IfStatement', 'if (x > 5)', [
new AstNode('Echo', 'print(x)'),
]),
]);
echo var_export($ast, true);注意事项
注意事项
var_export()无法处理包含闭包(Closure)或资源(Resource)类型的属性- 只能导出 public 属性,private/protected 属性不会出现在
$properties数组中 - 未实现
__set_state()的自定义类使用var_export()时会产生警告 eval()执行导出代码存在安全风险,应优先使用文件缓存方式(require)__set_state()必须声明为static(PHP 8.0+ 可使用返回类型static)
小贴士
- 在框架中,
var_export()+require是比serialize()更快的缓存方案,因为 PHP 源码可以被 opcache 缓存 - 适用于路由缓存、配置缓存、DI 容器缓存等不频繁变更的数据
最佳实践
1. 配合文件缓存使用
php
<?php
declare(strict_types=1);
class CacheableConfig
{
public function save(string $path): void
{
$content = '<?php return ' . var_export($this, true) . ';' . PHP_EOL;
file_put_contents($path, $content, LOCK_EX);
}
public static function load(string $path): ?static
{
if (is_file($path)) {
return require $path;
}
return null;
}
}2. 避免使用 eval()
php
<?php
declare(strict_types=1);
// 错误方式 - 有安全风险
$code = var_export($object, true);
eval('$restored = ' . $code . ';');
// 正确方式 - 写入文件后 require
$code = '<?php return ' . var_export($object, true) . ';';
file_put_contents('/tmp/cache.php', $code);
$restored = require '/tmp/cache.php';3. 处理不可导出的属性
php
<?php
declare(strict_types=1);
class ServiceWithClosure
{
public string $name;
public \Closure $action;
public function __construct(string $name, \Closure $action)
{
$this->name = $name;
$this->action = $action;
}
public static function __set_state(array $properties): static
{
// Closure 无法被 var_export 导出,需手动处理
return new static(
$properties['name'],
fn() => null, // 默认空操作
);
}
}4. 在抽象基类中定义 __set_state
php
<?php
declare(strict_types=1);
abstract class BaseEntity
{
public int $id;
public static function __set_state(array $properties): static
{
$instance = new static();
foreach ($properties as $key => $value) {
if (property_exists($instance, $key)) {
$instance->$key = $value;
}
}
return $instance;
}
}