Wzorzec Proxy kontroluje dostęp do innego obiektu. Klient myśli że rozmawiał z prawdziwym obiektem, a w rzeczywistości rozmawiał z pośrednikiem który może dodać cache, logowanie, lazy loading, kontrolę dostępu albo zdalny dostęp. W Magento 2 Proxy to wbudowany mechanizm – każda klasa z sufiksem \Proxy w kontenerze DI to właśnie ten wzorzec.
Rodzaje Proxy
| Typ | Cel | Przykład |
|---|---|---|
| Virtual Proxy | Lazy loading – opóźnione tworzenie obiektu | Magento DI \Proxy |
| Caching Proxy | Cache wyników metod | Cache warstwa na Repository |
| Protection Proxy | Kontrola dostępu | Sprawdzenie uprawnień przed operacją |
| Remote Proxy | Dostęp do zdalnego obiektu | Klient HTTP jako proxy do zewnętrznego API |
Virtual Proxy – lazy loading
<?php
declare(strict_types=1);
interface ReportGeneratorInterface
{
public function generate(array $params): string;
public function getAvailableFormats(): array;
}
// Prawdziwy obiekt - inicjalizacja jest kosztowna (połączenie z DB, ładowanie konfiguracji)
class HeavyReportGenerator implements ReportGeneratorInterface
{
private array $config;
private \PDO $connection;
public function __construct()
{
echo "Inicjalizacja generatora (kosztowna operacja)...\n";
// Symulacja kosztownej inicjalizacji
sleep(0); // w prawdziwym kodzie: połączenie z DB, ładowanie szablonów itp.
$this->config = $this->loadConfig();
}
public function generate(array $params): string
{
return "Raport: " . json_encode($params);
}
public function getAvailableFormats(): array
{
return ['pdf', 'csv', 'xlsx'];
}
private function loadConfig(): array
{
return ['max_rows' => 10000, 'timeout' => 30];
}
}
// Virtual Proxy - tworzy obiekt dopiero gdy naprawdę potrzebny
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
{
// Inicjalizacja następuje tu - tylko gdy faktycznie generujemy raport
return $this->getReal()->generate($params);
}
public function getAvailableFormats(): array
{
// Ta metoda też odkłada inicjalizację
return $this->getReal()->getAvailableFormats();
}
}
// Klient nie wie czy ma do czynienia z Proxy czy prawdziwym obiektem
$generator = new LazyReportGeneratorProxy();
echo "Proxy utworzony - generator NIE jest jeszcze zainicjalizowany\n";
// HeavyReportGenerator::__construct() jeszcze nie uruchomiony
$formats = $generator->getAvailableFormats(); // Tu dopiero inicjalizacja
Caching Proxy
<?php
declare(strict_types=1);
interface ProductRepositoryInterface
{
public function getById(int $id): array;
public function getByCategory(int $categoryId): array;
}
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] = [
'data' => $this->real->getById($id),
'expires' => time() + $this->ttl,
];
}
if ($this->cache[$key]['expires'] < time()) {
unset($this->cache[$key]);
return $this->getById($id);
}
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 w Magento 2 – generowane automatycznie
<?php
// Magento generuje Proxy dla ciężkich klas których nie chcemy inicjalizować
// przy każdym żądaniu - nawet jeśli nie są używane
// W di.xml lub przez konwencję nazewniczą:
// Zamiast wstrzyknąć HeavyService bezpośrednio:
class MyModule
{
// Bez Proxy: HeavyService inicjalizowany przy KAŻDYM żądaniu
public function __construct(
private HeavyService $service
) {}
}
// Z Proxy: HeavyService inicjalizowany TYLKO gdy używany
class MyModuleWithProxy
{
public function __construct(
private HeavyService\Proxy $service // Magento DI wygeneruje tę klasę
) {}
public function doSomething(): void
{
if ($this->someCondition()) {
// Dopiero tu HeavyService jest faktycznie tworzony
$this->service->expensiveOperation();
}
}
}
<!-- di.xml - zarejestruj Proxy ręcznie -->
<type name="Vendor\Module\Model\MyService">
<arguments>
<argument name="heavyDependency" xsi:type="object">
Vendor\Module\Model\HeavyService\Proxy
</argument>
</arguments>
</type>
Podsumowanie
Proxy to opakowywanie obiektu w pośrednika który kontroluje dostęp do oryginału. Virtual Proxy opóźnia inicjalizację – kluczowe dla wydajności w Magento 2 gdzie niektóre serwisy są ciężkie ale rzadko używane. Caching Proxy dodaje warstwę cache bez zmiany interfejsu. Magento generuje klasy Proxy automatycznie – wystarczy dodać sufiks \Proxy przy wstrzykiwaniu zależności.
