PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Wzorzec Proxy – lazy loading, kontrola dostępu, caching, Proxy w Magento 2

by Henryk Tews / wtorek, 10 sierpnia 2021 / Opublikowano w Magento 2, Wzorce projektowe

Proxy to wzorzec który stawia pośrednika między klientem a prawdziwym obiektem. Pośrednik implementuje ten sam interfejs co oryginał, więc klient nie widzi różnicy – a Proxy może robić coś dodatkowego: opóźniać inicjalizację, kontrolować dostęp, logować wywołania lub keszować wyniki. W Magento 2 Proxy jest generowane automatycznie dla klas z ciężkimi konstruktorami.

Problem bez Proxy – ciężka inicjalizacja

<?php

// Klasa która przy inicjalizacji robi dużo pracy
class HeavyReportGenerator
{
    private array $cachedData = [];

    public function __construct(
        private DatabaseConnection $db,
        private FileSystem $fs,
        private CacheInterface $cache
    ) {
        // Ciężki konstruktor - ładuje całą konfigurację raportów przy starcie
        $this->cachedData = $this->db->fetchAll('SELECT * FROM report_config');
    }

    public function generate(string $type): string
    {
        // ...generowanie raportu
        return 'report content';
    }
}

// Problem: każda klasa która wstrzykuje HeavyReportGenerator
// ponosi koszt ciężkiego konstruktora nawet jeśli
// nigdy nie wywoła generate()
class OrderController
{
    public function __construct(
        private HeavyReportGenerator $reportGenerator, // inicjalizowany zawsze
        private OrderRepository $orderRepository
    ) {}

    public function index(): void
    {
        // $this->reportGenerator jest tu nigdy nieużywany
        // ale i tak był inicjalizowany przy tworzeniu kontrolera
    }
}

Lazy Loading Proxy – implementacja

<?php

declare(strict_types=1);

interface ReportGeneratorInterface
{
    public function generate(string $type): string;
}

class HeavyReportGenerator implements ReportGeneratorInterface
{
    public function __construct(
        private DatabaseConnection $db
    ) {
        // Ciężka inicjalizacja - zapytanie do bazy przy każdym new
        sleep(0); // symulacja
    }

    public function generate(string $type): string
    {
        return "Raport typu: {$type}";
    }
}

// Proxy z leniwą inicjalizacją
class LazyReportGeneratorProxy implements ReportGeneratorInterface
{
    private ?HeavyReportGenerator $realGenerator = null;

    public function __construct(
        private DatabaseConnection $db
    ) {
        // Konstruktor Proxy jest lekki - nic nie inicjalizuje
    }

    private function getRealGenerator(): HeavyReportGenerator
    {
        // Prawdziwy obiekt tworzony dopiero przy pierwszym wywołaniu metody
        if ($this->realGenerator === null) {
            $this->realGenerator = new HeavyReportGenerator($this->db);
        }

        return $this->realGenerator;
    }

    public function generate(string $type): string
    {
        return $this->getRealGenerator()->generate($type);
    }
}

Proxy w Magento 2 – automatyczne generowanie

Magento 2 generuje klasy Proxy automatycznie dla klas z ciężkimi konstruktorami. Wystarczy zadeklarować zależność jako KlasaName\Proxy w konstruktorze lub w di.xml:

<?php

namespace Vendor\Module\Model;

// Zamiast wstrzykiwać ciężką klasę bezpośrednio
class OrderExporter
{
    public function __construct(
        // Magento wygeneruje Proxy który opóźni inicjalizację
        private \Magento\Catalog\Model\Product\Action\Proxy $productAction,
        private \Magento\Reports\Model\ResourceModel\Report\Collection\Proxy $reportCollection
    ) {
        // Konstruktor jest teraz lekki - Proxy nie inicjalizuje prawdziwych obiektów
    }

    public function export(array $orderIds): void
    {
        // Prawdziwe obiekty tworzone dopiero tutaj, przy pierwszym użyciu
        $this->productAction->updateAttributes(
            $orderIds,
            ['export_status' => 1],
            0
        );
    }
}

Możesz też wskazać Proxy przez di.xml:

<?xml version="1.0"?>
<config>
    <type name="Vendor\Module\Model\OrderExporter">
        <arguments>
            <argument name="productAction" xsi:type="object">
                Magento\Catalog\Model\Product\Action\Proxy
            </argument>
        </arguments>
    </type>
</config>

Protection Proxy – kontrola dostępu

<?php

declare(strict_types=1);

interface CustomerRepositoryInterface
{
    public function getById(int $id): CustomerInterface;
    public function save(CustomerInterface $customer): CustomerInterface;
    public function delete(CustomerInterface $customer): bool;
}

// Proxy kontrolujący dostęp na podstawie uprawnień
class SecureCustomerRepositoryProxy implements CustomerRepositoryInterface
{
    public function __construct(
        private CustomerRepositoryInterface $repository,
        private AuthorizationInterface $authorization
    ) {}

    public function getById(int $id): CustomerInterface
    {
        // Odczyt - wystarczą podstawowe uprawnienia
        $this->checkPermission('Vendor_Module::customer_view');
        return $this->repository->getById($id);
    }

    public function save(CustomerInterface $customer): CustomerInterface
    {
        // Zapis - wymagane wyższe uprawnienia
        $this->checkPermission('Vendor_Module::customer_edit');
        return $this->repository->save($customer);
    }

    public function delete(CustomerInterface $customer): bool
    {
        // Usunięcie - tylko administrator
        $this->checkPermission('Vendor_Module::customer_delete');
        return $this->repository->delete($customer);
    }

    private function checkPermission(string $resource): void
    {
        if (!$this->authorization->isAllowed($resource)) {
            throw new \Magento\Framework\Exception\AuthorizationException(
                __('You do not have permission to perform this action.')
            );
        }
    }
}

Caching Proxy – keszowanie wyników

<?php

declare(strict_types=1);

// Proxy keszujący wyniki kosztownych operacji
class CachingProductRepositoryProxy implements ProductRepositoryInterface
{
    private array $cache = [];

    public function __construct(
        private ProductRepositoryInterface $repository,
        private \Magento\Framework\Cache\FrontendInterface $cacheFrontend
    ) {}

    public function getById(int $id, bool $editMode = false, ?int $storeId = null, bool $forceReload = false): ProductInterface
    {
        $cacheKey = "product_{$id}_{$storeId}";

        if (!$forceReload && isset($this->cache[$cacheKey])) {
            return $this->cache[$cacheKey]; // in-memory cache
        }

        $cached = $this->cacheFrontend->load($cacheKey);
        if (!$forceReload && $cached) {
            $product = unserialize($cached);
            $this->cache[$cacheKey] = $product;
            return $product;
        }

        $product = $this->repository->getById($id, $editMode, $storeId, $forceReload);

        // Zapisz w cache na 1 godzinę
        $this->cacheFrontend->save(serialize($product), $cacheKey, [], 3600);
        $this->cache[$cacheKey] = $product;

        return $product;
    }

    // Pozostałe metody interfejsu delegują do oryginalnego repozytorium
    public function get(string $sku, bool $editMode = false, ?int $storeId = null, bool $forceReload = false): ProductInterface
    {
        return $this->repository->get($sku, $editMode, $storeId, $forceReload);
    }

    public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria): \Magento\Framework\Api\SearchResultsInterface
    {
        return $this->repository->getList($searchCriteria);
    }

    public function save(ProductInterface $product): ProductInterface
    {
        $result = $this->repository->save($product);
        // Unieważnij cache po zapisie
        unset($this->cache["product_{$product->getId()}_"]);
        return $result;
    }

    public function delete(ProductInterface $product): bool
    {
        $id = $product->getId();
        $result = $this->repository->delete($product);
        unset($this->cache["product_{$id}_"]);
        return $result;
    }

    public function deleteById(string $sku): bool
    {
        return $this->repository->deleteById($sku);
    }
}

Proxy vs Decorator – gdzie różnica?

Oba wzorce opakowują obiekt i implementują ten sam interfejs. Różnica jest w intencji:

Aspekt Proxy Decorator
Intencja Kontrola dostępu do obiektu Rozszerzenie funkcjonalności
Wiedza o oryginale Sam tworzy lub dostaje oryginał Zawsze dostaje oryginał z zewnątrz
Typowe zastosowania Lazy loading, cache, auth Dodawanie warstw logiki
W Magento 2 Generowane klasy Proxy w generated/ System pluginów (Interceptor)

Podsumowanie

Proxy to jeden z wzorców który w Magento 2 jest wbudowany w sam framework – generowane klasy Proxy w katalogu generated/ to dokładnie ten wzorzec w akcji. Rozumienie go pomaga świadomie decydować kiedy wstrzyknąć KlasaName\Proxy zamiast samej klasy i kiedy samodzielnie napisać Proxy dla kontroli dostępu lub keszowania.

About Henryk Tews

Co możesz przeczytać następne

Checkout customization – własne pola, mixin JS, krok do procesu, zapis do Order
UI Components – siatka danych, DataProvider, własne kolumny, konfiguracja XML
Command i Chain of Responsibility w PHP – wzorce behawioralne
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}