Magento 2: explicación práctica de qué es una clase proxy?

17

Entonces, sé teóricamente qué es una clase proxy en Magento 2. Leí el increíble artículo de Alan Storm sobre él y entiendo totalmente cómo se generan esas clases.

Sin embargo, y no sé si es porque soy un hablante no nativo de inglés o si las explicaciones de Alan están usando clases no básicas que son muy abstractas, pero me cuesta entender cómo funciona y especialmente cuándo usar durante el desarrollo

Así que tomemos este ejemplo del núcleo en 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>

Me gustaría saber:

  • ¿Por qué se usa una clase proxy en ese caso particular?
  • ¿Cuándo, en general, se debe usar una clase proxy?
Raphael en Digital Pianism
fuente

Respuestas:

17

Este uso particular no es un buen ejemplo del uso del patrón Proxy. Creo que incluso es inútil en ese fragmento de código en particular, ya que una colección no realiza ninguna operación de base de datos a menos que se llame al método de carga. Si su observador se usaría en la clase de comando de consola como la dependencia, entonces tiene sentido usar un proxy.

La clase proxy solo debe usarse cuando durante la construcción del objeto ejecuta una operación costosa. Un buen ejemplo son los comandos de consola de Symfony:

Imagine que su comando de consola está utilizando ProductRepository como una dependencia. El constructor del repositorio de productos establece la conexión MySQL a la base de datos del catálogo.

Significa que en cada bin/magentollamada, sin importar qué comando ejecute, las dependencias del repositorio serán instanciadas. Entonces, la única forma de evitarlo es usar una instanciación perezosa del objeto original creando un proxy. En este caso la base de datos, la conexión a la base de datos del catálogo se establecerá solo cuando llame a un método de repositorio.

Espero que ayude a comprender mejor la idea de proxy.

Ivan Chepurnyi
fuente
1
El hecho de que el ejemplo que elegí sea inútil me confundió aún más. Nuevamente teóricamente entiendo el concepto. Pero lo que no entiendo: ¿por qué agregarías ProductRepository como una dependencia al comando de la consola si no lo usas para cada comando? ¿No debería ser una dependencia solo para los comandos que usa? Según lo que dijo, ¿el Proxy es una forma de "saltear" una dependencia? Pero en ese caso, ¿por qué es una dependencia en primer lugar?
Raphael en Digital Pianism
1
Creo que el comando de consola de Symfony es un gran ejemplo, ya que debes hablar con Magento desde allí, y la única forma de hacerlo es especificar una dependencia en el constructor. En el componente de consola de Symfony, debe crear una clase para cada comando. Esta clase tiene métodos de configuración y ejecución. Configurar establece su nombre y argumentos, mientras que ejecutar realmente ejecuta operaciones costosas. Si se ejecuta una operación costosa en la configuración, entonces está jodido, es por eso que el proxy es la respuesta a este problema.
Ivan Chepurnyi
13

Una clase proxy le permite inyectar una clase de dependencia que no necesariamente necesitará, y que tiene un alto costo asociado con hacerlo.

Si observa un proxy que Magento ha generado, \Magento\Framework\View\Layout\Proxyverá que tiene los mismos métodos que la clase original. La diferencia es que cada vez que se llama a alguno de ellos, comprueba si la clase de la que es un proxy realmente se ha instanciado y crea el objeto si no. (Esto sucede en un _getSubject()o_getCache() método ).

Es una carga lenta para la inyección de dependencia.

Debe usar un proxy si su clase no siempre utiliza una dependencia de clase y:

  • Tiene muchas dependencias propias, o
  • Su constructor involucra código de uso intensivo de recursos, o
  • Inyectarlo tiene efectos secundarios

Un buen ejemplo de esto son las sesiones. Obtener sesiones a través del ObjectManager es una mala práctica, pero inyectar una clase de sesión como \Magento\Customer\Model\Sessionpodría romper las cosas si su clase alguna vez se ejecuta fuera del alcance de esa sesión (digamos que inyecta la sesión de cliente frontend en una página de administración). Lo solucionas inyectando el proxy de la sesión\Magento\Customer\Model\Session\Proxy y solo haga referencia a él cuando sepa que es válido. A menos que lo haga referencia, la sesión nunca se instancia y nada se rompe.

En su ejemplo específico de di.xml, parece que usaron el proxy para justificar la inyección de un controlador en lugar de la fábrica de ese controlador. De cualquier manera, eso no es para lo que se pretende usar los proxies, y el beneficio de ello en esa situación es probablemente mínimo.

Ryan Hoerr
fuente
7

Los proxies autogenerados de tipo Magento 2 se pueden usar para "corregir" errores de diseño. Eso puede ser muy útil. Hay 2 casos de uso:

  1. Envuelva un gráfico de objetos caros que la persona dependiente podría no necesitar cada vez.

  2. Romper una dependencia cíclica donde la clase Adepende By la clase Bdepende de un A.
    La inyección B\Proxyen Ale permite instantiate A, que luego a su vez puede ser utilizado para crear una instancia Bcuando se utiliza realmente con el verdadero Aobjeto.

En el caso de 1. la dependencia que no siempre se usa es una señal de que la clase dependiente hace demasiado, o tal vez hace demasiado por un método. El comando de consola @ivan mencionado es un buen ejemplo de eso.

En el caso de 2. No conozco una forma genérica de romper esa dependencia. Tiendo a reescribir si hay tiempo, pero eso podría no ser una opción.

Solo como nota al margen, me gustaría agregar que hay muchos más tipos de proxies en OOP que la instanciación perezosa autogenerada que usa Magento 2 (por ejemplo, proxy remoto).

Vinaí
fuente
Hola @ vinai, ¿cuál es la forma de usar las clases proxy a través del método __constructor () o di.xml.?
akgola
1
De acuerdo con las pautas de codificación de Magento, los proxies de la sección 2.5 NO DEBEN declararse en los constructores de clase. Los proxies DEBEN declararse en di.xml. Ver devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai
1

Aquí están las respuestas

¿Por qué se usa una clase proxy en ese caso particular?

Si observa de cerca el código que está escrito para la clase "SetConversionValueObserver", si Google Adwards no está activo "return" y si no hay ningún pedido "return". Significa que el objeto de recopilación de pedidos se creará solo cuando existan identificadores de pedido y los adwords de Google estén activos. si inyectamos la clase de colección de pedidos real, el administrador de objetos crea un objeto de colección con sus objetos de clase principal sin saber que los adwords de Google no están activos y que ralentizan la página de éxito del pedido. por lo tanto, mejor crear objetos a pedido que es el uso de 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;
}

¿Cuándo, en general, se debe usar una clase proxy? - Inyecte la clase Proxy cuando crea que la creación de objetos será costosa y que el constructor de la clase es particularmente intensivo en recursos. - cuando no desea un impacto innecesario en el rendimiento debido a la creación de objetos. - cuando sienta que la creación de objetos debería ocurrir cuando llama a un método particular en una condición particular, no siempre. Por ejemplo, el constructor Layout requiere muchos recursos.

Real Layout constructor 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 constructor, eche un vistazo, no se llama al constructor padre, así como solo se pasa el nombre de la clase de diseño para que la creación real del objeto ocurra cuando se llama al método.

 /**
 * 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 clase proxy tiene un método para crear objetos a pedido, _subject es el objeto de la clase pasada.

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

Y método llamado usando _subject.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
MukeshphpMysql
fuente