Proxy — controlling access, lazy loading
Concept
Proxy pattern provides a surrogate or placeholder for another object. The Proxy controls access to the original object, allowing it to intercept operations before or after they reach the target.
Types of Proxy:
- Virtual Proxy (Lazy Loading): Delays expensive object creation until it's needed.
$user->postsin Eloquent is a virtual proxy — the posts query only runs when the property is accessed. - Protection Proxy: Controls access based on permissions. Check authorization before delegating to the real object.
- Remote Proxy: Represents an object in a different address space or server. An RPC stub is a remote proxy.
- Caching Proxy: Caches results of expensive operations. Returns cached results without calling the real object.
- Logging Proxy: Records method calls for debugging or auditing. (Similar to Decorator, but the intent is different — Proxy controls access, Decorator adds behavior.)
Structure: Proxy and real subject implement the same interface. Proxy holds a reference to the real subject (or creates it lazily). Clients interact with the Proxy, unaware they're not talking to the real thing.
Proxy vs Decorator: Both wrap an object. Proxy CONTROLS access to the object (and often creates it lazily). Decorator ADDS behavior to an object. The intent differs: Proxy is about access control/deferral; Decorator is about extension.
Proxy in PHP: __get(), __call(), __set() magic methods can implement transparent proxies. Dynamic proxies using Reflection can intercept any method call.
Code Example
<?php
interface ImageInterface
{
public function render(): string;
public function getSize(): int;
}
// Real subject — expensive to load
class HeavyImage implements ImageInterface
{
private string $data;
public function __construct(private readonly string $path)
{
// Expensive: reads file, decodes, processes
$this->data = file_get_contents($path);
echo "Image loaded from disk: {$path}\n"; // for demo
}
public function render(): string { return "<img src='data:image/jpg;base64," . base64_encode($this->data) . "'>"; }
public function getSize(): int { return strlen($this->data); }
}
// Virtual Proxy — lazy loading
class LazyImageProxy implements ImageInterface
{
private ?HeavyImage $realImage = null;
public function __construct(private readonly string $path) {}
private function loadRealImage(): HeavyImage
{
if ($this->realImage === null) {
$this->realImage = new HeavyImage($this->path); // created on first access
}
return $this->realImage;
}
public function render(): string { return $this->loadRealImage()->render(); }
public function getSize(): int { return $this->loadRealImage()->getSize(); }
}
// Protection Proxy — authorization check
class AuthorizationProxy implements ImageInterface
{
public function __construct(
private readonly ImageInterface $real,
private readonly string $userRole,
private readonly string $requiredPermission = 'view_image',
) {}
public function render(): string
{
if (!$this->hasPermission()) {
throw new \RuntimeException("Access denied. Required permission: {$this->requiredPermission}");
}
return $this->real->render();
}
public function getSize(): int
{
if (!$this->hasPermission()) throw new \RuntimeException('Access denied.');
return $this->real->getSize();
}
private function hasPermission(): bool
{
$permissions = ['admin' => ['view_image', 'delete_image'], 'user' => ['view_image']];
return in_array($this->requiredPermission, $permissions[$this->userRole] ?? []);
}
}
// Caching Proxy
class CachingProxy implements ImageInterface
{
private ?string $cachedRender = null;
public function __construct(private readonly ImageInterface $real) {}
public function render(): string
{
if ($this->cachedRender === null) {
$this->cachedRender = $this->real->render(); // render once, cache
}
return $this->cachedRender;
}
public function getSize(): int { return $this->real->getSize(); }
}
// Usage — stacking proxies
$image = new AuthorizationProxy(
new CachingProxy(
new LazyImageProxy('/images/hero.jpg')
),
userRole: 'user'
);
// Image not loaded yet
echo $image->render(); // loads image, renders, caches
echo $image->render(); // returns cached render, no disk read