Lazy Objects (PHP 8.4)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches' (d5). Detection requires profiling tools like Blackfire or Symfony Profiler (as listed in detection_hints.tools) to identify eager instantiation of expensive services that could benefit from lazy objects. Standard linters won't flag this performance pattern.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates wrapping expensive objects with lazy initialization via ReflectionClass or proxy-manager. This typically requires modifying DI container configuration and potentially service definitions, but remains localized to the container setup rather than touching all consuming code.
Closest to 'localised tax' (b3). Lazy objects primarily affect the DI container/service layer. Once configured, consuming code is unaffected — the whole point is transparent initialization. The choice applies to web and CLI contexts but remains contained within infrastructure code rather than spreading throughout the codebase.
Closest to 'notable trap' (t5). The misconception field explicitly states developers confuse lazy objects with general lazy loading, missing that these are VM-level proxies. Common mistakes include using lazy objects for always-accessed services (wasted overhead), misunderstanding ghost vs proxy semantics, and serialization edge cases. These are documented gotchas that experienced developers learn.
Also Known As
TL;DR
Explanation
PHP 8.4 adds ReflectionClass::newLazyProxy() and ReflectionClass::newLazyGhost() to the core. A lazy ghost is the real object class with initialisation deferred; a lazy proxy wraps a real instance created on demand. Both defer expensive construction (DB connections, API clients, heavy config parsing) until the first property access or method call. This is the pattern that DI containers have implemented manually for years — now it is a first-class language feature.
Common Misconception
Why It Matters
Common Mistakes
- Using lazy objects for services that are always accessed — the proxy overhead is wasted if initialisation always happens.
- Not understanding that lazy ghost requires the real class, while lazy proxy can wrap any compatible class.
- Serialising lazy objects before initialisation — behaviour depends on whether the object has been triggered.
- Triggering initialisation in the constructor of the surrounding class — defeats the purpose.
Code Examples
// Eager construction — all services built even if unused:
class OrderController {
public function __construct(
private ReportingService $reporting, // Heavy — connects to BI DB
private AuditService $audit, // Heavy — reads config files
private NotificationService $notify, // Heavy — loads templates
) {} // All three built on every request, even simple reads
}
// PHP 8.4 lazy proxy — only constructed if accessed:
$container->bind(ReportingService::class, function() {
$reflector = new ReflectionClass(ReportingService::class);
return $reflector->newLazyProxy(
fn() => new ReportingService($this->get(BiDatabase::class))
);
});
// OrderController receives the proxy
// ReportingService only constructed when $this->reporting->method() called