Proxy Pattern
debt(d7/e5/b3/t5)
Closest to 'only careful code review or runtime testing' (d7). PHPStan (listed) won't flag missing proxy abstraction or business logic leaking into a proxy; this is a design concern that requires code review.
Closest to 'touches multiple files / significant refactor in one component' (e5). Introducing or correcting a proxy means extracting an interface, adjusting the real subject, the proxy class, and likely all call sites/factories — not a one-line fix.
Closest to 'localised tax' (b3). Per applies_to, a proxy is a structural pattern around a specific subject; it adds an indirection layer maintainers must keep in sync with the real interface, but its reach stays within that component.
Closest to 'notable trap most devs eventually learn' (t5). Per misconception, developers routinely conflate proxy with decorator since both share an interface and wrap a subject; the distinction (controlled access vs added behaviour) is a well-documented gotcha.
Also Known As
TL;DR
Explanation
The Proxy pattern places an intermediary in front of a real object, implementing the same interface. Types: Virtual Proxy (defers expensive object creation until needed — lazy loading), Caching Proxy (caches results of operations on the real object), Protection Proxy (enforces access control), and Remote Proxy (represents an object in a different process or server). In PHP, Doctrine ORM uses virtual proxies for lazy-loaded entities. The Proxy differs from Decorator in intent: Decorator adds behaviour, Proxy controls access. PHP's magic methods (__get, __call) enable dynamic proxy creation.
Common Misconception
Why It Matters
Common Mistakes
- Not implementing the same interface as the real subject — callers must change to use the proxy.
- Proxy that adds logic the real object should have — the proxy should be transparent infrastructure, not business logic.
- Virtual proxies that initialise the real object eagerly — defeating the lazy loading purpose.
- Confusing proxy (same interface, controlled access) with decorator (same interface, added behaviour) — they serve different purposes.
Code Examples
// Direct access — no caching, no access control:
class ReportService {
public function getExpensiveReport(int $id): Report {
return $this->db->runHeavyQuery($id); // Called every time
}
}
// A CachingReportProxy would intercept and cache without changing ReportService
interface Image {
public function render(): string;
}
class RealImage implements Image {
public function __construct(private string $path) {
$this->loadFromDisk(); // expensive
}
public function render(): string { return "<img src='{$this->path}'>"; }
}
// Lazy-loading proxy — defers expensive load until render() is first called
class LazyImage implements Image {
private ?RealImage $real = null;
public function __construct(private string $path) {}
public function render(): string {
$this->real ??= new RealImage($this->path);
return $this->real->render();
}
}