Magento 2: praktyczne wyjaśnienie, czym jest klasa proxy?

17

Wiem więc teoretycznie, czym jest klasa proxy w Magento 2. Przeczytałem o niej niesamowity artykuł Alan Storm i całkowicie rozumiem, w jaki sposób te klasy są generowane.

Jednak i nie wiem, czy to dlatego, że nie jestem rodzimym językiem angielskim, czy też wyjaśnienia Alana używają nie-podstawowych klas, które są bardzo abstrakcyjne, ale trudno mi zrozumieć, jak to działa, a zwłaszcza kiedy używać to podczas rozwoju.

Weźmy ten przykład z rdzenia w app/code/Magento/GoogleAdwords/etc/di.xml:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\GoogleAdwords\Observer\SetConversionValueObserver">
        <arguments>
            <argument name="collection" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Collection\Proxy</argument>
        </arguments>
    </type>
</config>

Chciałbym wiedzieć:

  • dlaczego w tym konkretnym przypadku używana jest klasa proxy ?
  • kiedy ogólnie należy używać klasy proxy?
Raphael at Digital Pianism
źródło

Odpowiedzi:

17

To szczególne użycie nie jest dobrym przykładem użycia wzorca proxy. Myślę, że jest to nawet bezużyteczne w tym konkretnym fragmencie kodu, ponieważ kolekcja nie wykonuje żadnych operacji DB, chyba że zostanie wywołana metoda load. Jeśli ich obserwator byłby używany w klasie poleceń konsoli jako zależność, wówczas sensowne jest użycie proxy.

Klasy proxy należy używać tylko wtedy, gdy podczas budowy obiektu wykonujesz kosztowną operację. Dobrym przykładem są polecenia konsoli Symfony:

Wyobraź sobie, że twoje polecenie konsoli używa zależności ProductRepository. Konstruktor repozytorium produktów ustanawia połączenie MySQL z bazą danych katalogu.

Oznacza to, że przy każdym bin/magentowywołaniu, bez względu na to, jakie polecenie wykonasz, zależności od repozytorium zostaną utworzone. Jedynym sposobem, aby tego uniknąć, jest użycie leniwej instancji oryginalnego obiektu przez utworzenie serwera proxy. W takim przypadku połączenie z bazą danych katalogu zostanie nawiązane tylko po wywołaniu metody repozytorium.

Mam nadzieję, że pomoże to lepiej zrozumieć ideę proxy.

Ivan Chepurnyi
źródło
1
Fakt, że wybrany przeze mnie przykład jest bezużyteczny, jeszcze bardziej mnie pogubił. Znowu teoretycznie rozumiem pojęcie. Ale czego nie rozumiem: dlaczego miałbyś dodawać ProductRepository jako zależność do polecenia konsoli, jeśli nie używasz go do każdego polecenia. Czy nie powinno to zależeć tylko od używanych poleceń? Zgodnie z tym, co powiedziałeś, proxy to sposób na „pominięcie” zależności? Ale w takim razie, dlaczego ta zależność jest przede wszystkim?
Raphael at Digital Pianism
1
Myślę, że polecenie konsoli Symfony jest świetnym przykładem, ponieważ musisz z niego porozmawiać z Magento, a jedynym sposobem na to jest określenie zależności w konstruktorze. W komponencie konsoli Symfony musisz utworzyć klasę dla każdego pojedynczego polecenia. Ta klasa ma metody konfiguracji i wykonywania. Program Configure ustawia swoją nazwę i argumenty, podczas gdy execute faktycznie wykonuje kosztowną operację. Jeśli przy konfiguracji wykonywane są kosztowne operacje, a nie wkręcasz się, dlatego proxy jest odpowiedzią na ten problem.
Ivan Chepurnyi
13

Klasa proxy pozwala wstrzykiwać zależności, które niekoniecznie będą potrzebne, a to wiąże się z wysokimi kosztami.

Jeśli spojrzysz na serwer proxy wygenerowany przez Magento, \Magento\Framework\View\Layout\Proxyzobaczysz, że ma wszystkie te same metody, co klasa oryginalna. Różnica polega na tym, że za każdym razem, gdy którekolwiek z nich jest wywoływane, sprawdza, czy klasa, której jest proxy, faktycznie została utworzona, i tworzy obiekt, jeśli nie. (Dzieje się tak w metodzie _getSubject()lub _getCache()).

Leniwe ładowanie do wstrzykiwania zależności.

Powinieneś użyć serwera proxy, jeśli klasa nie zawsze używa zależności od klasy i:

  • Ma wiele własnych zależności lub
  • Jego konstruktor wymaga kodu wymagającego dużych zasobów lub
  • Wstrzyknięcie ma skutki uboczne

Dobrym tego przykładem są sesje. Uzyskiwanie sesji przez ObjectManager to zła praktyka, ale wstrzykiwanie klasy sesji, tak jak \Magento\Customer\Model\Sessionmogłoby to zepsuć, jeśli twoja klasa kiedykolwiek działa poza zakresem tej sesji (powiedz, że wstrzykujesz sesję klienta frontonu na stronie administratora). Można obejść ten \Magento\Customer\Model\Session\Proxyproblem , wstrzykując zamiast tego serwer proxy sesji i odwołując się do niego tylko wtedy, gdy wiesz, że jest prawidłowy. O ile nie odwołujesz się do niego, sesja nigdy nie jest tworzona i nic się nie psuje.

W twoim konkretnym przykładzie di.xmlwygląda na to, że użyli proxy do uzasadnienia wstrzyknięcia kontrolera, a nie fabryki tego kontrolera. Tak czy inaczej, nie do tego służą proxy, a korzyść z tego w tej sytuacji jest prawdopodobnie minimalna.

Ryan Hoerr
źródło
7

Automatycznie generowane serwery proxy typu Magento 2 mogą być używane do „naprawy” błędów projektowych. To może być bardzo przydatne. Istnieją 2 przypadki użycia:

  1. Zawiń kosztowny wykres obiektowy, który może nie być potrzebny każdorazowo przez osobę zależną.

  2. Przerwij cykliczną zależność, w której klasa Azależy od, Ba klasa Bzależy od A.
    Wstrzykiwanie B\Proxydo Aumożliwia tworzenie instancji A, która z kolei może być używana do tworzenia instancji, Bgdy jest ona rzeczywiście używana z rzeczywistym Aobiektem.

W przypadku 1. zależności, która nie zawsze jest używana, jest to znak, że klasa osób zależnych robi za dużo, a może za dużo jedną metodą. Wspomniane polecenie konsoli @ivan jest tego dobrym przykładem.

W przypadku 2. Nie znam ogólnego sposobu na przełamanie tej zależności. Mam tendencję do przepisywania, jeśli jest czas, ale może to nie być opcja.

Na marginesie, chciałbym dodać, że w OOP jest o wiele więcej typów serwerów proxy niż automatycznie generowana leniwa instancja używana przez Magento 2 (np. Zdalne proxy).

Vinai
źródło
Witaj @ vinai, jaki jest sposób korzystania z klas proxy za pomocą metody __constructor () lub di.xml.?
akgola
1
Zgodnie z wytycznymi kodowania Magento sekcja 2.5 proxy NIE MOŻE być deklarowana w konstruktorach klas. Serwery proxy MUSZĄ zostać zadeklarowane w pliku di.xml. Zobacz devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai
1

Oto odpowiedzi

dlaczego w tym konkretnym przypadku używana jest klasa proxy?

Jeśli przyjrzysz się uważnie kodowi napisanemu dla klasy „SetConversionValueObserver”, jeśli Google Adwards nie jest aktywne „return” i jeśli nie ma zamówienia „return”. Oznacza to, że obiekt kolekcji zamówień zostanie utworzony tylko wtedy, gdy istnieją identyfikatory zamówień i aktywne są adwords Google. jeśli wstrzykniemy rzeczywistą klasę kolekcji zamówień, menedżer obiektów utworzy obiekt kolekcji z obiektami klasy nadrzędnej, nie wiedząc, że Google adwords jest nieaktywny i że strona spowalniająca zamówienie jest wolniejsza. więc lepiej tworzyć obiekty na żądanie, czyli korzystanie z proxy. /vendor/magento/module-google-adwords/Observer/SetConversionValueObserver.php

 /**
 * Set base grand total of order to registry
 *
 * @param \Magento\Framework\Event\Observer $observer
 * @return \Magento\GoogleAdwords\Observer\SetConversionValueObserver
 */
public function execute(\Magento\Framework\Event\Observer $observer)
{
    if (!($this->_helper->isGoogleAdwordsActive() && $this->_helper->isDynamicConversionValue())) {
        return $this;
    }
    $orderIds = $observer->getEvent()->getOrderIds();
    if (!$orderIds || !is_array($orderIds)) {
        return $this;
    }
    $this->_collection->addFieldToFilter('entity_id', ['in' => $orderIds]);
    $conversionValue = 0;
    /** @var $order \Magento\Sales\Model\Order */
    foreach ($this->_collection as $order) {
        $conversionValue += $order->getBaseGrandTotal();
    }
    $this->_registry->register(
        \Magento\GoogleAdwords\Helper\Data::CONVERSION_VALUE_REGISTRY_NAME,
        $conversionValue
    );
    return $this;
}

kiedy ogólnie należy używać klasy proxy? - Wstrzyknij klasę Proxy, gdy uważasz, że tworzenie obiektów będzie kosztowne, a konstruktor klasy wymaga dużych nakładów. - gdy nie chcesz niepotrzebnego wpływu na wydajność z powodu tworzenia obiektów. - gdy czujesz, że tworzenie obiektu powinno nastąpić, gdy wywołasz określoną metodę w określonym stanie, nie zawsze. Na przykład Konstruktor układu wymaga dużych zasobów.

Rzeczywisty konstruktor układu a układ / serwer proxy

public function __construct(
    Layout\ProcessorFactory $processorFactory,
    ManagerInterface $eventManager,
    Layout\Data\Structure $structure,
    MessageManagerInterface $messageManager,
    Design\Theme\ResolverInterface $themeResolver,
    Layout\ReaderPool $readerPool,
    Layout\GeneratorPool $generatorPool,
    FrontendInterface $cache,
    Layout\Reader\ContextFactory $readerContextFactory,
    Layout\Generator\ContextFactory $generatorContextFactory,
    AppState $appState,
    Logger $logger,
    $cacheable = true,
    SerializerInterface $serializer = null
) {
    $this->_elementClass = \Magento\Framework\View\Layout\Element::class;
    $this->_renderingOutput = new \Magento\Framework\DataObject();
    $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class);

    $this->_processorFactory = $processorFactory;
    $this->_eventManager = $eventManager;
    $this->structure = $structure;
    $this->messageManager = $messageManager;
    $this->themeResolver = $themeResolver;
    $this->readerPool = $readerPool;
    $this->generatorPool = $generatorPool;
    $this->cacheable = $cacheable;
    $this->cache = $cache;
    $this->readerContextFactory = $readerContextFactory;
    $this->generatorContextFactory = $generatorContextFactory;
    $this->appState = $appState;
    $this->logger = $logger;
}

Konstruktor proxy, spójrz, nie wywoływano żadnego konstruktora nadrzędnego, a także przekazano nazwę klasy układu, aby rzeczywiste tworzenie obiektu miało miejsce po wywołaniu metody.

 /**
 * Proxy constructor
 *
 * @param \Magento\Framework\ObjectManagerInterface $objectManager
 * @param string $instanceName
 * @param bool $shared
 */
public function __construct(
    \Magento\Framework\ObjectManagerInterface $objectManager,
    $instanceName = \Magento\Framework\View\Layout::class,
    $shared = true
) {
    $this->_objectManager = $objectManager;
    $this->_instanceName = $instanceName;
    $this->_isShared = $shared;
}

Klasa proxy ma metodę tworzenia obiektu na żądanie, _subject jest obiektem przekazywanej klasy.

/**
 * Get proxied instance
 *
 * @return \Magento\Framework\View\Layout
 */
protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get($this->_instanceName)
            : $this->_objectManager->create($this->_instanceName);
    }
    return $this->_subject;
}

I metoda wywołana przy użyciu _subject.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
MukeshphpMysql
źródło