← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Lazy Objects (PHP 8.4)

PHP PHP 8.4+ Advanced
debt(d5/e3/b3/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

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.

e3 Effort Remediation debt — work required to fix once spotted

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.

b3 Burden Structural debt — long-term weight of choosing wrong

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.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.

About DEBT scoring →

Also Known As

lazy proxy lazy ghost lazy initialization ReflectionClass lazy

TL;DR

PHP 8.4 native lazy object proxies defer initialisation until first access — enabling zero-cost dependency injection of services that may never be used in a request.

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

Lazy objects are just lazy loading — lazy objects are native VM-level proxies; property access on a lazy object transparently triggers initialisation with no interface changes to the consuming code.

Why It Matters

A DI container that injects 50 services into a controller only needs to construct the 3 actually used per request — lazy objects make this automatic at the language level.

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

✗ Vulnerable
// 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
}
✓ Fixed
// 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

Added 16 Mar 2026
Edited 22 Mar 2026
Views 49
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 2 pings T 1 ping F 3 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
PetalBot 1
Amazonbot 7 Scrapy 5 Ahrefs 4 Perplexity 3 ChatGPT 3 SEMrush 3 Unknown AI 2 Claude 2 Google 1 Bing 1 PetalBot 1
crawler 30 crawler_json 2
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Use lazy initialisation for expensive objects in DI containers — PHP 8.4 adds native lazy object support via ReflectionClass; otherwise use proxy generators like ocramius/proxy-manager
📦 Applies To
PHP 8.4+ web cli symfony laravel
🔗 Prerequisites
🔍 Detection Hints
Expensive service instantiated eagerly on every request even when not used; DI container initialising all services unconditionally
Auto-detectable: ✗ No blackfire symfony-profiler
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: Medium ✗ Manual fix Fix: Medium Context: Class Tests: Update


✓ schema.org compliant