Magento 2: spiegazione pratica di cos'è una classe proxy?


17

Quindi, so teoricamente che cos'è una classe proxy in Magento 2. Ho letto il fantastico articolo di Alan Storm su di esso e capisco perfettamente come vengono generate quelle classi.

Tuttavia, e non so se è perché sono un madrelingua inglese o se le spiegazioni di Alan stanno usando classi non core che sono molto astratte, ma sto facendo fatica a capire come funziona e specialmente quando usare durante lo sviluppo.

Quindi prendiamo questo esempio dal nucleo in 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>

Mi piacerebbe sapere:

  • perché una classe proxy viene utilizzata in quel caso particolare?
  • quando, in generale, si dovrebbe usare una classe proxy?

Risposte:


17

Questo particolare utilizzo non è un buon esempio dell'uso del modello Proxy. Penso che sia persino inutile in quel particolare pezzo di codice, poiché una raccolta non sta eseguendo alcuna operazione DB a meno che non venga chiamato il metodo load. Se il loro osservatore verrebbe usato nella classe di comandi della console come dipendenza, allora ha senso usare un proxy.

La classe proxy deve essere utilizzata solo quando durante la costruzione dell'oggetto si esegue un'operazione costosa. Un buon esempio sono i comandi della console di Symfony:

Immagina che il comando della tua console stia utilizzando ProductRepository come dipendenza. Il costruttore del repository di prodotti stabilisce una connessione MySQL al database del catalogo.

Significa che ad ogni bin/magentochiamata, indipendentemente dal comando eseguito, le dipendenze del repository verranno istanziate. Quindi l'unico modo per evitarlo è usare un'istanza pigra dell'oggetto originale creando un proxy. In questo caso, la connessione al database del catalogo verrà stabilita solo quando si chiama un metodo di repository.

Spero che questo aiuti a capire meglio l'idea del proxy.


1
Il fatto che l'esempio che ho scelto sia inutile mi ha reso ancora più confuso. Ancora una volta teoricamente capisco il concetto. Ma cosa non capisco: perché dovresti aggiungere ProductRepository come dipendenza al comando console se non lo usi per ogni comando. Non dovrebbe essere una dipendenza solo per i comandi che usi? Secondo quello che hai detto, il proxy è un modo per "saltare" una dipendenza? Ma in quel caso, perché è una dipendenza in primo luogo?
Raffaello al Pianismo digitale,

1
Penso che il comando della console di Symfony sia un ottimo esempio, dato che devi parlarne con Magento, e l'unico modo per farlo è specificare una dipendenza nel costruttore. Nel componente della console di Symfony è necessario creare una classe per ogni singolo comando. Questa classe ha i metodi di configurazione ed esecuzione. Configura imposta il nome e gli argomenti, mentre execute esegue operazioni costose. Se un'operazione configurata viene eseguita su configure, di quello che si è fregato, ecco perché il proxy è la risposta a questo problema.
Ivan Chepurnyi,

13

Una classe proxy consente di iniettare dipendenze una classe che non sarà necessariamente necessaria e che ha un costo elevato associato.

Se guardi un proxy che Magento ha generato, come \Magento\Framework\View\Layout\Proxy, vedrai che ha tutti gli stessi metodi della classe originale. La differenza è che ogni volta che uno di questi viene chiamato, controlla se la classe di cui è un proxy è stata effettivamente istanziata e crea l'oggetto in caso contrario. (Ciò accade in un metodo _getSubject()o _getCache().)

È un caricamento lento per l'iniezione di dipendenza.

È necessario utilizzare un proxy se una dipendenza di classe non è sempre utilizzata dalla propria classe e:

  • Ha molte dipendenze proprie o
  • Il suo costruttore coinvolge codice ad alta intensità di risorse, o
  • L'iniezione ha effetti collaterali

Un buon esempio di ciò sono le sessioni. Ottenere sessioni attraverso l'ObjectManager è una cattiva pratica, ma iniettare una classe di sessione come \Magento\Customer\Model\Sessionpotrebbe spezzare le cose se la tua classe viene eseguita al di fuori dell'ambito di quella sessione (supponi di iniettare la sessione del cliente frontend su una pagina di amministrazione). Puoi aggirare ciò iniettando \Magento\Customer\Model\Session\Proxyinvece il proxy della sessione e facendo riferimento a esso solo quando sai che è valido. A meno che non lo si faccia riferimento, la sessione non viene mai istanziata e nulla si interrompe.

Nel tuo esempio specifico di di.xml, sembra che abbiano usato il proxy per giustificare l'iniezione di un controller piuttosto che la fabbrica di quel controller. In entrambi i casi, non è per questo che i proxy sono destinati a essere utilizzati e il vantaggio in tale situazione è probabilmente minimo.


7

I proxy generati automaticamente di tipo Magento 2 possono essere utilizzati per "correggere" errori di progettazione. Questo può essere molto utile. Esistono 2 casi d'uso:

  1. Avvolgere un grafico oggetto costoso che potrebbe non essere necessario ogni volta dal dipendente.

  2. Rompere una dipendenza ciclica da cui Adipende la Bclasse e la classe Bdipende da A.
    L'iniezione B\Proxyin Aconsente di creare un'istanza A, che a sua volta può essere utilizzata per creare un'istanza Bquando viene effettivamente utilizzata con l' Aoggetto reale .

In caso di 1. la dipendenza che non viene sempre utilizzata è un segno che la classe di dipendente fa molto o forse fa molto con un metodo. Il comando console @ivan menzionato ne è un buon esempio.

In caso di 2. Non conosco un modo generico per spezzare quella dipendenza. Tendo a riscrivere se c'è tempo, ma potrebbe non essere un'opzione.

Come nota a margine, vorrei aggiungere che ci sono molti più tipi di proxy in OOP rispetto all'istanza pigra auto-generata che Magento 2 usa (ad esempio proxy remoto).


Ciao @ vinai, qual è il modo di usare le classi proxy tramite il metodo __constructor () o tramite di.xml.?
akgola,

1
In base alle linee guida sulla codifica Magento, la sezione 2.5 non deve essere dichiarata nei costruttori di classe. I proxy DEVONO essere dichiarati in di.xml. Vedi devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai

1

Ecco le risposte

perché una classe proxy viene utilizzata in quel caso particolare?

Se dai un'occhiata più da vicino al codice scritto per la classe "SetConversionValueObserver", se Google Adwards non è attivo "return" e se non vi è alcun ordine "return". Significa, l'oggetto Raccolta ordini verrà creato solo quando esistono ID ordine e Google AdWords attivo. se inseriamo la classe di raccolta ordini effettiva, il gestore oggetti crea l'oggetto di raccolta con i suoi oggetti della classe padre senza conoscere le parole d'ordine di Google non attive e rallentare la pagina di successo dell'ordine. quindi, meglio creare oggetto su richiesta che è l'uso del 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;
}

quando, in generale, si dovrebbe usare una classe proxy? - Iniettare la classe proxy quando ritieni che la creazione di oggetti sia costosa e il costruttore della classe disponga di un uso intensivo di risorse. - quando non si desidera un impatto sulle prestazioni non necessario a causa della creazione di oggetti. - quando ritieni che la creazione di oggetti dovrebbe avvenire quando chiami un metodo particolare in una condizione particolare non sempre. Ad esempio, il costruttore del layout richiede molte risorse.

Costruttore layout effettivo 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;
}

Costruttore proxy, dai un'occhiata, nessun costruttore principale chiamato così come appena passato il nome della classe di layout in modo che la creazione effettiva dell'oggetto avvenga quando viene chiamato il metodo

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

La classe proxy ha un metodo per creare oggetti su richiesta, _subject è l'oggetto della classe passata.

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

E metodo chiamato usando _subject.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.