symfony/var-exporter
Exports serializable PHP values to fast, OPcache-friendly PHP code, preserving serialization semantics and references. Includes DeepCloner for efficient deep cloning and ProxyHelper to generate lazy-loading proxies; uses ext-deepclone (or polyfill) for speed.
Installation:
composer require symfony/var-exporter
Ensure ext-deepclone is installed for optimal performance, or use the polyfill.
First Use Case: Export a complex object to reusable PHP code:
use Symfony\Component\VarExporter\VarExporter;
$user = new User('John', ['role' => 'admin']);
$exported = VarExporter::export($user);
// Output: `new User('John', ['role' => 'admin'])`
Where to Look First:
VarExporter::export() for serialization.DeepCloner for cloning objects efficiently.ProxyHelper for lazy-loading abstract/internal classes.$exported = VarExporter::export($entity, VarExporter::EXPORT_DEBUG);
Log::debug('Entity state:', ['data' => $exported]);
DeepCloner$cloner = new DeepCloner($prototype);
$clone1 = $cloner->clone(); // Reuses memory for strings/arrays
$clone2 = $cloner->clone();
Request objects).$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(AbstractService::class));
eval('class AbstractServiceLazyProxy'.$proxyCode);
$service = AbstractServiceLazyProxy::createLazyProxy(
initializer: fn() => new ConcreteService(/* ... */)
);
DatabaseConnection) until first use.VarExporter and DeepCloner as singletons:
$this->app->singleton(DeepCloner::class, fn() => new DeepCloner());
illuminate\queue\FailedJob events for debugging:
use Symfony\Component\VarExporter\VarExporter;
public function handle(FailedJob $event)
{
$exported = VarExporter::export($event->job->payload());
Log::error('Failed job:', ['payload' => $exported]);
}
$cacheKey = 'config_export_' . md5(serialize($config));
$exported = Cache::remember($cacheKey, 3600, fn() => VarExporter::export($config));
eval($exported); // Rehydrate when needed
Class Not Found Exceptions:
ClassNotFoundException. Use VarExporter::EXPORT_NOT_SERIALIZABLE to skip them:
VarExporter::export($obj, VarExporter::EXPORT_NOT_SERIALIZABLE);
Readonly Properties:
VarExporter::EXPORT_HYDRATE_READONLY to force hydration:
VarExporter::export($obj, VarExporter::EXPORT_HYDRATE_READONLY);
Circular References:
DeepCloner handles circular references, but deep exports may hit recursion limits. Use VarExporter::EXPORT_CYCLIC:
VarExporter::export($obj, VarExporter::EXPORT_CYCLIC);
Lazy Proxies and __unserialize:
__unserialize directly on lazy proxies. Use ProxyHelper::generateLazyProxy() with a custom initializer.Performance with ext-deepclone:
ext-deepclone, DeepCloner falls back to slower methods. Benchmark with:
$time = microtime(true);
$clone = DeepCloner::deepClone($obj);
Log::debug('Cloning took:', [microtime(true) - $time]);
Inspect Exported Code:
Use VarExporter::export($obj, VarExporter::EXPORT_DEBUG) to see the generated PHP code.
Validate Hydration: After exporting/hydrating, validate the object state:
$hydrated = VarExporter::hydrate($exported);
assert($hydrated instanceof User);
assert($hydrated->name === 'John');
Lazy Proxy Debugging:
Override createLazyProxy to log initialization:
$proxy = AbstractServiceLazyProxy::createLazyProxy(
initializer: fn() => {
Log::debug('Initializing AbstractService');
return new ConcreteService();
}
);
Custom Exporters:
Extend VarExporter to handle custom types:
class CustomExporter extends VarExporter
{
public static function export($value, int $flags = 0): string
{
if ($value instanceof CustomClass) {
return 'new CustomClass(' . self::export($value->data) . ')';
}
return parent::export($value, $flags);
}
}
DeepCloner Callbacks:
Use DeepCloner::setCallback() to modify cloning behavior:
$cloner = new DeepCloner();
$cloner->setCallback(CustomClass::class, fn($obj) => clone $obj->withNewData());
Proxy Customization:
Modify ProxyHelper output by subclassing and overriding generateLazyProxy():
class CustomProxyHelper extends ProxyHelper
{
protected function generateProxyClass(ReflectionClass $class): string
{
// Custom logic here
return parent::generateProxyClass($class);
}
}
Blade Templates:
Avoid exporting objects directly in Blade (use VarExporter in controllers instead):
// Controller (safe)
$exported = VarExporter::export($user);
return view('user', ['exported' => $exported]);
// Blade (unsafe: eval in templates is discouraged)
@php eval($exported); @endphp
Queue Jobs: Export job payloads for debugging failed jobs:
public function handle(FailedJob $event)
{
$payload = VarExporter::export($event->job->payload(), VarExporter::EXPORT_DEBUG);
Log::error('Failed job payload:', ['payload' => $payload]);
}
Eloquent Models:
Use VarExporter to debug relationships:
$user = User::with('posts')->find(1);
$exported = VarExporter::export($user, VarExporter::EXPORT_DEBUG);
Log::debug('User with posts:', ['data' => $exported]);
How can I help you explore Laravel packages today?