Magento 2: Praktische Erklärung einer Proxy-Klasse


16

Ich weiß theoretisch, was eine Proxy-Klasse in Magento 2 ist. Ich habe den großartigen Alan Storm-Artikel darüber gelesen und verstehe vollkommen, wie diese Klassen generiert werden.

Und ich weiß nicht, ob es daran liegt, dass ich kein englischer Muttersprachler bin oder ob Alan in seinen Erklärungen sehr abstrakte Nebenklassen verwendet. Es fällt mir jedoch schwer zu verstehen, wie das funktioniert und wann ich es verwenden soll es während der Entwicklung.

Nehmen wir also dieses Beispiel aus dem Kern von 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>

Ich würde gerne wissen:

  • Warum wird in diesem speziellen Fall eine Proxy-Klasse verwendet ?
  • Wann sollte man im Allgemeinen eine Proxy-Klasse verwenden?

Antworten:


17

Diese spezielle Verwendung ist kein gutes Beispiel für die Verwendung eines Proxy-Musters. Ich denke, dass es in diesem bestimmten Teil des Codes sogar nutzlos ist, da eine Sammlung keine DB-Operationen ausführt, es sei denn, die Lademethode wird aufgerufen. Wenn ihr Beobachter in der Konsolenbefehlsklasse als Abhängigkeit verwendet wird, ist die Verwendung eines Proxys sinnvoll.

Die Proxy-Klasse sollte nur verwendet werden, wenn Sie während der Erstellung des Objekts eine teure Operation ausführen. Ein gutes Beispiel sind Symfony-Konsolenbefehle:

Stellen Sie sich vor, Ihr Konsolenbefehl verwendet ProductRepository als Abhängigkeit. Der Produkt-Repository-Konstruktor stellt eine MySQL-Verbindung zur Katalogdatenbank her.

Dies bedeutet, dass bei jedem bin/magentoAufruf, unabhängig davon, welchen Befehl Sie ausführen, die Repository-Abhängigkeiten instanziiert werden. Die einzige Möglichkeit, dies zu vermeiden, ist die Verwendung der verzögerten Instanziierung des ursprünglichen Objekts durch Erstellen eines Proxys. In diesem Fall wird die Verbindung zur Katalogdatenbank nur hergestellt, wenn Sie eine Repository-Methode aufrufen.

Hoffe, das hilft, die Idee des Proxy besser zu verstehen.


1
Die Tatsache, dass das Beispiel, das ich gewählt habe, nutzlos ist, hat mich noch mehr verwirrt. Wieder theoretisch verstehe ich das Konzept. Aber was ich nicht verstehe: Warum sollten Sie ProductRepository als Abhängigkeit zum Konsolenbefehl hinzufügen, wenn Sie es nicht für jeden Befehl verwenden? Sollte es nicht eine Abhängigkeit nur für die Befehle sein, die Sie verwenden? Laut dem, was Sie gesagt haben, ist Proxy eine Möglichkeit, eine Abhängigkeit zu "überspringen"? Aber warum ist das in diesem Fall überhaupt eine Abhängigkeit?
Raphael bei Digital Pianism

1
Ich denke, der Symfony-Konsolenbefehl ist ein großartiges Beispiel, da Sie mit Magento darüber sprechen müssen. Der einzige Weg, dies zu tun, besteht darin, eine Abhängigkeit im Konstruktor anzugeben. In der Symfony-Konsolenkomponente müssen Sie für jeden einzelnen Befehl eine Klasse erstellen. Diese Klasse verfügt über Methoden zum Konfigurieren und Ausführen. Configure legt seinen Namen und seine Argumente fest, während execute eine teure Operation ausführt. Wenn teure Operationen auf configure ausgeführt werden, ist Proxy die Antwort auf dieses Problem.
Ivan Chepurnyi

12

Mit einer Proxy-Klasse können Sie eine Klasse abhängig machen, die Sie nicht unbedingt benötigen und die mit hohen Kosten verbunden ist.

Wenn Sie sich einen Proxy ansehen, den Magento generiert hat \Magento\Framework\View\Layout\Proxy, werden Sie feststellen, dass er dieselben Methoden wie die ursprüngliche Klasse hat. Der Unterschied besteht darin, dass bei jedem Aufruf überprüft wird, ob die Klasse, von der es sich um einen Proxy handelt, tatsächlich instanziiert wurde, und das Objekt erstellt wird, wenn dies nicht der Fall ist. (Dies geschieht in einer _getSubject()oder _getCache()Methode.)

Es ist ein fauler Ladevorgang für die Abhängigkeitsinjektion.

Sie sollten einen Proxy verwenden, wenn eine Klassenabhängigkeit nicht immer von Ihrer Klasse verwendet wird.

  • Hat viele eigene Abhängigkeiten oder
  • Sein Konstruktor enthält ressourcenintensiven Code
  • Das Injizieren hat Nebenwirkungen

Ein gutes Beispiel hierfür sind Sitzungen. Es ist eine schlechte Praxis, Sitzungen über den ObjectManager zu erhalten, aber das Injizieren einer Sitzungsklasse wie \Magento\Customer\Model\Sessionkönnte zu Problemen führen, wenn Ihre Klasse jemals außerhalb des Gültigkeitsbereichs dieser Sitzung ausgeführt wird (sagen wir, Sie injizieren die Frontend-Kundensitzung auf einer Administrationsseite). Sie umgehen dies, indem Sie \Magento\Customer\Model\Session\Proxystattdessen den Proxy der Sitzung injizieren und ihn nur referenzieren, wenn Sie wissen, dass er gültig ist. Wenn Sie nicht darauf verweisen, wird die Sitzung niemals instanziiert und nichts wird unterbrochen.

In Ihrem konkreten Beispiel di.xmlsieht es so aus, als hätten sie den Proxy verwendet, um das Injizieren eines Controllers zu rechtfertigen, und nicht die Fabrik dieses Controllers. In beiden Fällen sind Proxies nicht dafür vorgesehen, und der Nutzen ist in dieser Situation wahrscheinlich minimal.


7

Autogenerierte Proxys vom Typ Magento 2 können verwendet werden, um Entwurfsfehler zu "beheben". Das kann sehr praktisch sein. Es gibt 2 Anwendungsfälle:

  1. Wickeln Sie einen teuren Objektgraphen ein, der möglicherweise nicht jedes Mal von der abhängigen Person benötigt wird.

  2. Brechen Sie eine zyklische Abhängigkeit, von der die Klasse Aabhängt Bund von der die Klasse Babhängt A.
    Durch Injizieren B\Proxyin Akönnen Sie eine Instanz Aerstellen, die wiederum verwendet werden kann, um eine Instanz zu erstellen, Bwenn sie tatsächlich mit dem realen AObjekt verwendet wird.

Im Fall von 1. ist die Abhängigkeit, die nicht immer verwendet wird, ein Zeichen dafür, dass die abhängige Klasse zu viel oder möglicherweise zu viel mit einer Methode tut. Der erwähnte Konsolenbefehl @ivan ist ein gutes Beispiel dafür.

Im Fall von 2. Ich kenne keinen generischen Weg, um diese Abhängigkeit aufzulösen. Ich neige dazu, umzuschreiben, wenn Zeit ist, aber das könnte keine Option sein.

Nur als Randnotiz möchte ich hinzufügen, dass es in OOP viel mehr Arten von Proxys gibt als die automatisch generierte verzögerte Instanziierung, die ein Magento 2 verwendet (z. B. Remote-Proxy).


Hallo @ Vinai, wie können Proxy-Klassen über die __constructor () -Methode oder über di.xml verwendet werden?
Akgola

1
Gemäß den Magento-Codierungsrichtlinien DARFEN in Klassenkonstruktoren KEINE Proxys deklariert werden. Proxies MÜSSEN in di.xml deklariert werden. Siehe devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai

1

Hier sind die Antworten

Warum wird in diesem speziellen Fall eine Proxy-Klasse verwendet?

Wenn Sie den folgenden Code, der für die Klasse "SetConversionValueObserver" geschrieben wurde, genau betrachten, wenn Google Adwards nicht aktiv ist, "return" und wenn es keine Bestellung gibt, "return". Das bedeutet, dass das Auftragserfassungsobjekt nur erstellt wird, wenn Auftrags-IDs vorhanden und Google AdWords aktiv sind. Wenn wir die tatsächliche Auftragserfassungsklasse einfügen, erstellt der Objektmanager ein Erfassungsobjekt mit seinen übergeordneten Klassenobjekten, ohne zu wissen, dass Google AdWords nicht aktiv ist und die Seite mit dem Auftragserfolg verlangsamt. Erstellen Sie daher besser ein Objekt nach Bedarf, das die Verwendung eines Proxys ist. /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;
}

Wann sollte man im Allgemeinen eine Proxy-Klasse verwenden? - Injizieren Sie die Proxy-Klasse, wenn Sie der Meinung sind, dass die Objekterstellung teuer ist und der Konstruktor der Klasse besonders ressourcenintensiv ist. - wenn Sie keine unnötigen Leistungseinbußen aufgrund der Objekterstellung wünschen. - Wenn Sie der Meinung sind, dass die Objekterstellung stattfinden sollte, wenn Sie eine bestimmte Methode in einer bestimmten Bedingung nicht immer aufrufen. Beispielsweise ist der Layoutkonstruktor ressourcenintensiv.

Tatsächlicher Layoutkonstruktor vs. Layout / 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;
}

Proxy-Konstruktor, werfen Sie einen Blick, es wird kein übergeordneter Konstruktor aufgerufen und nur der Name der Layout-Klasse übergeben, sodass die eigentliche Objekterstellung beim Aufruf der Methode erfolgt.

 /**
 * 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;
}

Die Proxy-Klasse verfügt über eine Methode zum Erstellen eines Objekts bei Bedarf. _Subject ist das Objekt der übergebenen Klasse.

/**
 * 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;
}

Und Methode mit _subject aufgerufen.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.