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()
会忽略它们); - 涉及
SplObjectStorage
、ArrayObject
或ArrayIterator
实例的引用会被保留; - 缺少类会抛出
ClassNotFoundException
,而不是反序列化为PHP_Incomplete_Class
对象; - 当
Reflection*
、IteratorIterator
和RecursiveIteratorIterator
类被序列化时,会抛出异常。
导出的数据是一个 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]);
实例化器还可以填充父类的属性。假设 Bar
是 Foo
的父类,并定义了一个 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],
]);
ArrayObject
、ArrayIterator
和 SplObjectHash
的实例可以使用特殊的 "\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]);
注水器还可以填充父类的属性。假设 Bar
是 Foo
的父类,并定义了一个 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]);
ArrayObject
、ArrayIterator
和 SplObjectHash
的实例可以使用特殊的 "\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 类。