Skip to content

__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 属性。对于 privateprotected 属性,需要在 __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 的工作流程

  1. 调用 var_export($object)var_export($object, true)
  2. PHP 检查对象类是否定义了 __set_state() 方法
  3. 如果已定义,生成 ClassName::__set_state(array(...)) 格式的代码
  4. 如果未定义,对内部类(如 stdClass)会使用特殊处理;对用户自定义类则产生警告
  5. 生成的代码可以被 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;
    }
}

参考链接