symfony/var-exporter
Symfony VarExporter lets you export any serializable PHP value to fast, OPcache-friendly PHP code (preserving __sleep/__wakeup, Serializable, __serialize). Includes Instantiator/Hydrator for bypassing constructors, deep cloning, and lazy-loading traits.
Installation:
composer require symfony/var-exporter
No additional configuration is needed—just autoload the package.
First Use Case: Export a complex object to PHP code for debugging, caching, or testing:
use Symfony\Component\VarExporter\VarExporter;
$user = new User(['name' => 'John', 'roles' => ['admin']]);
$exportedCode = VarExporter::export($user);
// Outputs: `return new User(['name' => 'John', 'roles' => ['admin']]);`
Where to Look First:
VarExporter::export() for serialization.Instantiator/Hydrator for object manipulation.var_dump() or serialize() for human-readable, reusable code.// Debugging a complex query result
$results = $userRepository->findAll();
file_put_contents('debug/results.php', '<?php return ' . VarExporter::export($results) . ';');
use Symfony\Component\VarExporter\Instantiator;
$user = Instantiator::instantiate(User::class, ['name' => 'Alice']);
// Skips constructor logic (e.g., validation, side effects).
use Symfony\Component\VarExporter\Hydrator;
$user = new User();
Hydrator::hydrate($user, ['name' => 'Bob']);
// For private properties (e.g., from a parent class):
Hydrator::hydrate($user, [], [
User::class => ['privateToken' => 'secret123'],
]);
unserialize(serialize($obj)) with memory-efficient cloning.use Symfony\Component\VarExporter\DeepCloner;
$cloner = new DeepCloner($originalObject);
$clone1 = $cloner->clone(); // Faster than serialize/unserialize.
use Symfony\Component\VarExporter\Proxy\ProxyHelper;
$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(User::class));
eval('class UserLazyProxy ' . $proxyCode);
$user = UserLazyProxy::createLazyProxy(function () {
return new User(['name' => 'Lazy User']);
});
// $user->getName() triggers the initializer.
$user = User::find(1);
$fixture = VarExporter::export($user);
$cached = cache()->get('user_1');
if (!$cached) {
$user = User::find(1);
$cached = VarExporter::export($user);
cache()->put('user_1', $cached, now()->addHour());
}
eval('return ' . $cached); // Reconstruct object.
Class Not Found Exceptions:
ClassNotFoundException is thrown (unlike serialize()).Readonly Properties:
Instantiator to set readonly properties during instantiation.Circular References:
VarExporter handles them, but deep cloning may still fail for complex graphs.DeepCloner for large objects to avoid memory issues.Lazy Proxies and eval:
eval(). In production, pre-generate and cache them.PHP 8.4+ Lazy Objects:
LazyGhostTrait/LazyProxyTrait. Update old code to use ProxyHelper.$exported = VarExporter::export($obj, VarExporter::EXPORT_DEBUG);
// Adds comments like `// class User { ... }`.
ReflectionProperty to verify property values after hydration:
$reflection = new ReflectionProperty(User::class, 'privateField');
$reflection->setAccessible(true);
$value = $reflection->getValue($user);
OPcache Benefits:
Exported code runs faster than unserialize() due to OPcache optimization.
Tip: Cache exported code in production (e.g., file_put_contents()).
DeepCloner vs. serialize():
DeepCloner is ~3x faster for large objects but requires PHP 7.4+.
Benchmark:
$start = microtime(true);
$clone = DeepCloner::deepClone($bigObject);
echo microtime(true) - $start; // Compare with serialize/unserialize.
Custom Exporters:
Extend VarExporter for domain-specific serialization:
class CustomExporter extends VarExporter {
public static function export($value, array $options = []) {
$options[VarExporter::EXPORT_DEBUG] = true;
return parent::export($value, $options);
}
}
Hydrator Callbacks: Use closures to transform values during hydration:
Hydrator::hydrate($user, [
'name' => fn($val) => strtoupper($val),
]);
Lazy Proxy Initializers: Add logic to initialize proxies dynamically:
$proxy = UserLazyProxy::createLazyProxy(function () {
return User::query()->findOrFail(1);
});
Eloquent Models:
Avoid exporting models with unserializable relationships (e.g., belongsToMany with custom accessors).
Fix: Use ->toArray() or ->fresh() before exporting.
Service Container:
Instantiator bypasses container bindings. Use app()->make() for dependency injection:
$user = Instantiator::instantiate(User::class);
$user->setRepository(app()->make(UserRepository::class));
Testing:
Export fixtures to database/fixtures/ and load them in DatabaseSeeder:
$fixture = file_get_contents('fixtures/user.php');
eval('User::insert(' . $fixture . ');');
How can I help you explore Laravel packages today?