跳到内容

VarExporter 组件

编辑此页

VarExporter 组件将任何可序列化的 PHP 数据结构导出为纯 PHP 代码,并允许实例化和填充对象,而无需调用其构造函数。

安装

1
$ composer require --dev symfony/var-exporter

注意

如果您在 Symfony 应用程序之外安装此组件,则必须在代码中 require vendor/autoload.php 文件,以启用 Composer 提供的类自动加载机制。阅读 这篇文章 了解更多详情。

导出/序列化变量

此组件的主要功能是将 PHP 数据结构序列化为纯 PHP 代码,类似于 PHP 的 var_export 函数

1
2
3
4
5
6
7
8
use Symfony\Component\VarExporter\VarExporter;

$exported = VarExporter::export($someVariable);
// store the $exported data in some file or cache system for later reuse
$data = file_put_contents('exported.php', '<?php return '.$exported.';');

// later, regenerate the original variable when you need it
$regeneratedVariable = require 'exported.php';

使用此组件而不是 serialize()igbinary 的原因是性能:得益于 OPcache,生成的代码比使用 unserialize()igbinary_unserialize() 显着更快,内存效率更高。

此外,还有一些细微的差异

  • 如果原始变量定义了它们,则所有与 serialize() 关联的语义(例如 __wakeup()__sleep()Serializable)都会被保留(var_export() 会忽略它们);
  • 涉及 SplObjectStorageArrayObjectArrayIterator 实例的引用会被保留;
  • 缺少类会抛出 ClassNotFoundException,而不是反序列化为 PHP_Incomplete_Class 对象;
  • Reflection*IteratorIteratorRecursiveIteratorIterator 类被序列化时,会抛出异常。

导出的数据是一个 PSR-2 兼容的 PHP 文件。考虑以下类层次结构示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class AbstractClass
{
    protected int $foo;
    private int $bar;

    protected function setBar($bar): void
    {
        $this->bar = $bar;
    }
}

class ConcreteClass extends AbstractClass
{
    public function __construct()
    {
        $this->foo = 123;
        $this->setBar(234);
    }
}

当使用 VarExporter 导出 ConcreteClass 数据时,生成的 PHP 文件如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
    $o = [
        clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\ConcreteClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\ConcreteClass')),
    ],
    null,
    [
        'Symfony\\Component\\VarExporter\\Tests\\AbstractClass' => [
            'foo' => [
                123,
            ],
            'bar' => [
                234,
            ],
        ],
    ],
    $o[0],
    []
);

实例化 & 注水 PHP 类

实例化器

此组件提供了一个实例化器,它可以创建对象并设置其属性,而无需调用其构造函数或任何其他方法

1
2
3
4
5
6
7
use Symfony\Component\VarExporter\Instantiator;

// creates an empty instance of Foo
$fooObject = Instantiator::instantiate(Foo::class);

// creates a Foo instance and sets one of its properties
$fooObject = Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);

实例化器还可以填充父类的属性。假设 BarFoo 的父类,并定义了一个 privateBarProperty 属性

1
2
3
4
5
6
use Symfony\Component\VarExporter\Instantiator;

// creates a Foo instance and sets a private property defined on its parent Bar class
$fooObject = Instantiator::instantiate(Foo::class, [], [
    Bar::class => ['privateBarProperty' => $propertyValue],
]);

ArrayObjectArrayIteratorSplObjectHash 的实例可以使用特殊的 "\0" 属性名称来定义它们的内部值来创建

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\VarExporter\Instantiator;

// creates an SplObjectStorage where $info1 is associated with $object1, etc.
$theObject = Instantiator::instantiate(SplObjectStorage::class, [
    "\0" => [$object1, $info1, $object2, $info2...],
]);

// creates an ArrayObject populated with $inputArray
$theObject = Instantiator::instantiate(ArrayObject::class, [
    "\0" => [$inputArray],
]);

注水器

有时,您可能想要填充已存在对象的属性,而不是填充尚不存在的对象(使用实例化器)。这是 Hydrator 的目标。以下是 Hydrator 的基本用法,用于填充对象的属性

1
2
3
4
use Symfony\Component\VarExporter\Hydrator;

$object = new Foo();
Hydrator::hydrate($object, ['propertyName' => $propertyValue]);

注水器还可以填充父类的属性。假设 BarFoo 的父类,并定义了一个 privateBarProperty 属性

1
2
3
4
5
6
7
8
9
use Symfony\Component\VarExporter\Hydrator;

$object = new Foo();
Hydrator::hydrate($object, [], [
    Bar::class => ['privateBarProperty' => $propertyValue],
]);

// alternatively, you can use the special "\0" syntax
Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]);

ArrayObjectArrayIteratorSplObjectHash 的实例可以使用特殊的 "\0" 属性名称来定义它们的内部值来填充

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\VarExporter\Hydrator;

// creates an SplObjectHash where $info1 is associated with $object1, etc.
$storage = new SplObjectStorage();
Hydrator::hydrate($storage, [
    "\0" => [$object1, $info1, $object2, $info2...],
]);

// creates an ArrayObject populated with $inputArray
$arrayObject = new ArrayObject();
Hydrator::hydrate($arrayObject, [
    "\0" => [$inputArray],
]);

创建惰性对象

惰性对象是实例化为空并在需要时填充的对象。当您的类中具有需要大量计算来确定其值的属性时,这尤其有用。在这种情况下,您可能希望仅在实际需要属性值时才触发属性值的处理。得益于此,如果您从未使用过此属性,则不会进行繁重的计算。VarExporter 组件捆绑了两个 traits,可帮助您在类中轻松实现此类机制。

LazyGhostTrait

幽灵对象是空对象,当第一次调用任何方法时,它们会看到其属性被填充。得益于 LazyGhostTrait,惰性机制的实现变得容易。只有当实际使用对象并且需要初始化时,才会调用 MyLazyObject::populateHash() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace App\Hash;

use Symfony\Component\VarExporter\LazyGhostTrait;

class HashProcessor
{
    use LazyGhostTrait;

    // This property may require a heavy computation to have its value
    public readonly string $hash;

    public function __construct()
    {
        self::createLazyGhost(initializer: $this->populateHash(...), instance: $this);
    }

    private function populateHash(array $data): void
    {
        // Compute $this->hash value with the passed data
    }
}

LazyGhostTrait 还允许将非惰性类转换为惰性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
namespace App\Hash;

use Symfony\Component\VarExporter\LazyGhostTrait;

class HashProcessor
{
    public readonly string $hash;

    public function __construct(array $data)
    {
        $this->populateHash($data);
    }

    private function populateHash(array $data): void
    {
        // ...
    }

    public function validateHash(): bool
    {
        // ...
    }
}

class LazyHashProcessor extends HashProcessor
{
    use LazyGhostTrait;
}

$processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance): void {
    // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
    $data = /** Retrieve required data to compute the hash */;
    $instance->__construct(...$data);
    $instance->validateHash();
});

虽然您从不查询 $processor->hash 值,但繁重的方法永远不会被触发。但是,$processor 对象仍然存在,可以在您的代码中使用,传递给方法、函数等。

遗憾的是,幽灵对象不能与抽象类或内部 PHP 类一起使用。然而,VarExporter 组件借助 虚拟代理 满足了这一需求。

LazyProxyTrait

虚拟代理的目的是与 幽灵对象 相同,但它们的内部行为完全不同。幽灵对象需要扩展基类,而虚拟代理则利用 Liskov 替换原则。该原则描述了如果两个对象实现相同的接口,则可以在不同的实现之间进行交换,而不会破坏应用程序。这就是虚拟代理所利用的优势。要使用虚拟代理,您可以利用 ProxyHelper 生成代理的类代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
namespace App\Hash;

use Symfony\Component\VarExporter\ProxyHelper;

interface ProcessorInterface
{
    public function getHash(): bool;
}

abstract class AbstractProcessor implements ProcessorInterface
{
    protected string $hash;

    public function getHash(): bool
    {
        return $this->hash;
    }
}

class HashProcessor extends AbstractProcessor
{
    public function __construct(array $data)
    {
        $this->populateHash($data);
    }

    private function populateHash(array $data): void
    {
        // ...
    }
}

$proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(AbstractProcessor::class));
// $proxyCode contains the actual proxy and the reference to LazyProxyTrait.
// In production env, this should be dumped into a file to avoid calling eval().
eval('class HashProcessorProxy'.$proxyCode);

$processor = HashProcessorProxy::createLazyProxy(initializer: function (): ProcessorInterface {
    $data = /** Retrieve required data to compute the hash */;
    $instance = new HashProcessor(...$data);

    // Do any operation you need here: call setters, getters, methods to validate the hash, etc.

    return $instance;
});

就像幽灵对象一样,虽然您从不查询 $processor->hash,但它的值不会被计算。与幽灵对象的主要区别在于,这次创建的是抽象类的代理。这也适用于内部 PHP 类。

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本