Компонент VarExporter
Дата обновления перевода 2025-08-19
Компонент VarExporter
Компонент VarExporter экспортирует всю структуру PHP-данных, поддающуюся сериализации, в чистый PHP-код и позволяет инстанциировать и населять объекты без вызова к их конструкторам.
Установка
1
$ composer require --dev symfony/var-exporter
Note
Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно
подключить файл 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);
// сохраните $exported данные в каком-то файле или системе кеширования для дальнейшего повторного использования
$data = file_put_contents('exported.php', $exported);
// позже, регенерируйте изначальную переменную, когда она вам понадобится
$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);
}
}
При экспорте данных ConcreteClass
с помощью VarExporter, сгенерированный 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-классов
Инстанциатор
Этот компонент предоставляет инстанциатор, который может создавать объекты и устанавливать
их свойства без вызова их конструкторов или каких-либо других методов;:
use SymfonyComponentVarExporterInstantiator;
// создает пустой экземпляр Foo $fooObject = Instantiator::instantiate(Foo::class);
// создает экземпляр Foo и устанавливает одно из его свойств $fooObject = Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
Инстанциатор также может заполнить свойство родительского класса. Предположим, что
Bar
- это родительский класс Foo
, который определяет атрибут privateBarProperty
:
1 2 3 4 5 6
use Symfony\Component\VarExporter\Instantiator;
// создаёт экземпляр Foo и устанавливает приватное свойство, определённое в его родительском классе Bar
$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;
// Создаёт SplObjectHash, где $info1 ассоциируется с $object1, и т.д.
$theObject = Instantiator::instantiate(SplObjectStorage::class, [
"\0" => [$object1, $info1, $object2, $info2...],
]);
// создаёт ArrayObject, заполненный $inputArray
$theObject = Instantiator::instantiate(ArrayObject::class, [
"\0" => [$inputArray],
]);
Гидратор
Вместо того чтобы заполнять ещё несуществующие объекты (с помощью инстанциатора), иногда требуется заполнить свойства уже существующего объекта. Это цель 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],
]);
// как вариант, вы можете использовать специальный синтаксис "\0"
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;
// создаёт SplObjectHash, где $info1 ассоциируется с $object1 и т.д.
$storage = new SplObjectStorage();
Hydrator::hydrate($storage, [
"\0" => [$object1, $info1, $object2, $info2...],
]);
// созадёт ArrayObject, заполненный $inputArray
$arrayObject = new ArrayObject();
Hydrator::hydrate($arrayObject, [
"\0" => [$inputArray],
]);
Создание ленивых объектов
Ленивые объекты - это объекты, которые инстанцируются пустыми и заполняются по требованию. Это особенно полезно, когда в классах имеются, например, свойства, для определения значения которых требуются тяжёлые вычисления. В этом случае вам может потребоваться запускать обработку значения свойства только тогда, когда оно действительно необходимо. Благодаря этому, тяжёлые вычисления не будут производиться, если вы никогда не будете использовать данное свойство.
Начиная с версии 8.4, PHP поддерживает ленивые объекты через отображения API. Этот нативный API
работает с конкретными классами, но не с абстрактными или внутренними. Этот компонент предоставляет
помощников для генерирования ленивых объектов с помощью паттерна декоратора, который также работает
с абстрактными классами, внутренними классами и интерфейсами:
1 2 3 4 5 6 7 8 9 10 11 12
$proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(SomeInterface::class));
// $proxyCode должен быть сброшен в файл в окружениях производства
eval('class ProxyDecorator'.$proxyCode);
$proxy = ProxyDecorator::createLazyProxy(initializer: function (): SomeInterface {
// использовать любую сложную логику, которая вам тут нужна,
// чтобы вычислить $dependenies проксированного класса
$instance = new SomeHeavyClass(...$dependencies);
// вызвать сеттеры и т.д., если это необходимо
return $instance;
});
Используйте этот механизм только тогда, когда невозможно воспользоваться нативными ленивыми объектами (иначе вы получите предупреждение об устаревании).
Наследственное создание ленивых объектов
При использовании версии PHP старшей, чем 8.4, нативные ленивые объекты недоступны. В таких случаях компонент VarExporter предоставляет две черты, которые помогают рализовать механизмы ленивой загрузки в ваших классах.
LazyGhostTrait
7.3
LazyGhostTrait
устарела, начиная с Symfony 7.3. Используйте нативные ленивые
объекти PHP 8.4 вместо этого. Отметьте, что использование черты с версиями PHP
старше, чем 8.4, не вызывает устаревания, чтобы облегчить процесс перехода.
Объекты-призраки - это пустые объекты, свойства которых заполняются при первом вызове
любого метода. Благодаря LazyGhostTrait,
облегчается реализация ленивого механизма. В приведенном ниже примере свойство
$hash
определено как ленивое. Кроме того, метод MyLazyObject::computeHash()
должен вызываться только тогда, когда значение $hash
должно быть известно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\VarExporter\LazyGhostTrait;
class HashProcessor
{
use LazyGhostTrait;
// Ця властивість може вимагати важкого обчислення для отримання її значення
public readonly string $hash;
public function __construct()
{
self::createLazyGhost(initializer: $this->populateHash(...), instance: $this);
}
private function populateHash(array $data): void
{
// Вычислить значение $this->hash с переданными данными
}
}
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 {
// Сделать любую необходимую вам операцию здесь: вызвать геттеры, сеттеры, методы для валидации хеша и т.д.
$data = /** Извлечь необходимые для вычисления хеша данные */;
$instance->__construct(...$data);
$instance->validateHash();
});
Если вы никогда не будете запрашивать значение $processor->hash
, то тяжелые методы
никогда не будут вызваны. Но, тем не менее, объект $processor
существует и может
быть использован в вашем коде, передан методам, функциям и т.д.
Объекты-призраки, к сожалению, не могут работать с абстрактными классами или внутренними классами PHP. Тем не менее, компонент VarExporter покрывает эту потребность с помощью Виртуальных прокси .
LazyProxyTrait
7.3
LazyProxyTrait
устарела, начиная с Symfony 7.3. Используйте нативные ленивые
объекти PHP 8.4 вместо этого. Отметьте, что использование черты с версиями PHP
старше, чем 8.4, не вызывает устаревания, чтобы облегчить процесс перехода.
Назначение виртуальных прокси то же, что и у объектов-призраковs , но их внутреннее поведение совершенно иное. Там, где объекты-призраки требуют расширения базового класса, виртуальные прокси используют принцип замещения Лискова. Этот принцип описывает, что если два объекта реализуют один и тот же интерфейс, то можно поменять различные реализации местами без нарушения работы приложения. Именно это и используют виртуальные прокси. Для использования виртуальных прокси можно использовать 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 содержит реальный прокси и ссылку на LazyProxyTrait.
// В окружении производства это должно быть сброшено в файл, чтобы избежать вызова eval().
eval('class HashProcessorProxy'.$proxyCode);
$processor = HashProcessorProxy::createLazyProxy(initializer: function (): ProcessorInterface {
$data = /** Извлечь необходиме для вычисления хеша данные */;
$instance = new HashProcessor(...$data);
// Сделать любую необходимую вам операцию здесь: вызвать геттеры, сеттеры, методы для валидации хеша и т.д.
return $instance;
});
Подобно объектам-призракам, если вы никогда не запрашиваете $processor->hash
, то
его значение не будет вычислено. Основное отличие от объектов-призраков заключается в
том, что на этот раз создаётся прокси абстрактного класса. Это работает и с внутренними
классами PHP.