The Proxy pattern controls access to another object. The client thinks it is talking to the real object, but in reality it is talking to an intermediary that can add caching, logging, lazy loading, access control or remote access. In Magento 2 Proxy is a built-in mechanism – every class with the \Proxy suffix in the DI container is exactly this pattern.
Types of Proxy
| Type | Purpose | Example |
|---|---|---|
| Virtual Proxy | Lazy loading – delayed object creation | Magento DI \Proxy |
| Caching Proxy | Cache method results | Cache layer on Repository |
| Protection Proxy | Access control | Permission check before operation |
| Remote Proxy | Access to remote object | HTTP client as proxy to external API |
Virtual Proxy – lazy loading
<?php
declare(strict_types=1);
interface ReportGeneratorInterface
{
public function generate(array $params): string;
public function getAvailableFormats(): array;
}
class HeavyReportGenerator implements ReportGeneratorInterface
{
public function __construct()
{
echo "Initialising generator (expensive operation)...\n";
// Expensive initialisation: DB connection, loading templates etc.
}
public function generate(array $params): string
{
return "Report: " . json_encode($params);
}
public function getAvailableFormats(): array
{
return ['pdf', 'csv', 'xlsx'];
}
}
// Virtual Proxy - creates object only when actually needed
class LazyReportGeneratorProxy implements ReportGeneratorInterface
{
private ?ReportGeneratorInterface $real = null;
private function getReal(): ReportGeneratorInterface
{
if ($this->real === null) {
$this->real = new HeavyReportGenerator();
}
return $this->real;
}
public function generate(array $params): string
{
return $this->getReal()->generate($params);
}
public function getAvailableFormats(): array
{
return $this->getReal()->getAvailableFormats();
}
}
$generator = new LazyReportGeneratorProxy();
echo "Proxy created - generator NOT yet initialised\n";
// HeavyReportGenerator::__construct() has not run yet
$formats = $generator->getAvailableFormats(); // initialisation happens here
Caching Proxy
<?php
declare(strict_types=1);
class CachingProductRepositoryProxy implements ProductRepositoryInterface
{
private array $cache = [];
public function __construct(
private ProductRepositoryInterface $real,
private int $ttl = 3600,
) {}
public function getById(int $id): array
{
$key = "product_{$id}";
if (!isset($this->cache[$key]) || $this->cache[$key]['expires'] < time()) {
$this->cache[$key] = [
'data' => $this->real->getById($id),
'expires' => time() + $this->ttl,
];
}
return $this->cache[$key]['data'];
}
public function getByCategory(int $categoryId): array
{
$key = "category_{$categoryId}";
if (!isset($this->cache[$key]) || $this->cache[$key]['expires'] < time()) {
$this->cache[$key] = [
'data' => $this->real->getByCategory($categoryId),
'expires' => time() + $this->ttl,
];
}
return $this->cache[$key]['data'];
}
public function invalidate(int $productId): void
{
unset($this->cache["product_{$productId}"]);
}
}
Proxy in Magento 2 – automatically generated
<?php
// Magento generates Proxy for heavy classes we do not want to initialise
// on every request - even if they are not used
// With Proxy: HeavyService initialised ONLY when used
class MyModuleWithProxy
{
public function __construct(
private HeavyService\Proxy $service // Magento DI generates this class
) {}
public function doSomething(): void
{
if ($this->someCondition()) {
// Only here is HeavyService actually created
$this->service->expensiveOperation();
}
}
}
<!-- di.xml - register Proxy manually -->
<type name="Vendor\Module\Model\MyService">
<arguments>
<argument name="heavyDependency" xsi:type="object">
Vendor\Module\Model\HeavyService\Proxy
</argument>
</arguments>
</type>
Summary
Proxy wraps an object in an intermediary that controls access to the original. Virtual Proxy delays initialisation – critical for performance in Magento 2 where some services are heavy but rarely used. Caching Proxy adds a cache layer without changing the interface. Magento generates Proxy classes automatically – just add the \Proxy suffix when injecting dependencies.
