Magento 2: explicação prática do que é uma classe proxy?

17

Então, eu sei teoricamente o que é uma classe proxy no Magento 2. Eu li o incrível artigo de Alan Storm sobre isso e compreendo perfeitamente como essas classes são geradas.

No entanto, e não sei se é porque não sou um falante nativo de inglês ou se as explicações de Alan estão usando classes não essenciais que são muito abstratas, mas estou tendo dificuldade para entender como funciona e especialmente quando usar durante o desenvolvimento.

Então, vamos pegar este exemplo do núcleo em 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>

Eu gostaria de saber:

  • por que uma classe proxy é usada nesse caso específico?
  • quando, em geral, alguém deve usar uma classe proxy?
Raphael na Digital Pianism
fonte

Respostas:

17

Esse uso específico não é um bom exemplo de uso do padrão Proxy. Eu acho que é ainda inútil nesse pedaço de código específico, pois uma coleção não está executando nenhuma operação de banco de dados, a menos que o método load seja chamado. Se o observador deles fosse usado na classe de comando do console como dependência, faz sentido usar um proxy.

A classe proxy só deve ser usada quando, durante a construção do objeto, você executa uma operação cara. Um bom exemplo são os comandos do console Symfony:

Imagine que o comando do console esteja usando o ProductRepository como uma dependência. O construtor de repositório do produto estabelece a conexão MySQL ao banco de dados do catálogo.

Isso significa que em todas as bin/magentochamadas, não importa qual comando você execute, as dependências do repositório serão instanciadas. Portanto, a única maneira de evitá-lo é usar a instanciação lenta do objeto original criando um proxy. Nesse caso, a conexão com o banco de dados do catálogo será estabelecida somente quando você chamar um método de repositório.

Espero que ajude a entender melhor a idéia de proxy.

Ivan Chepurnyi
fonte
11
O fato de o exemplo que eu escolher ser inútil me deixou ainda mais confusa. Mais uma vez, teoricamente, eu entendo o conceito. Mas o que eu não entendo: por que você adicionaria ProductRepository como uma dependência ao comando do console se você não o usava para todos os comandos. Não deveria ser uma dependência apenas para os comandos que você usa? De acordo com o que você disse, o Proxy é uma maneira de "pular" uma dependência? Mas nesse caso, por que isso é uma dependência em primeiro lugar?
Raphael no Digital Pianism
11
Eu acho que o comando do console do Symfony é um ótimo exemplo, pois você deve conversar com o Magento, e a única maneira de fazer isso é especificar uma dependência no construtor. No componente do console Symfony, você deve criar uma classe para cada comando. Esta classe possui métodos de configuração e execução. O Configure define seu nome e argumentos, enquanto o execute está realmente executando uma operação cara. Se uma operação cara for executada no configure, você será ferrado, por isso o proxy é a resposta para esse problema.
Ivan Chepurnyi 28/10
13

Uma classe proxy permite injetar dependência em uma classe que você não precisará necessariamente, e que tem um alto custo associado a isso.

Se você olhar para um proxy que o Magento gerou, por exemplo \Magento\Framework\View\Layout\Proxy, verá que ele tem todos os mesmos métodos da classe original. A diferença é que toda vez que um desses é chamado, ele verifica se a classe de que é um proxy foi realmente instanciada e cria o objeto, se não. (Isso acontece em um_getSubject() ou _getCache()método.)

É carregamento lento para injeção de dependência.

Você deve usar um proxy se uma dependência de classe nem sempre for usada por sua classe e:

  • Possui muitas dependências próprias ou
  • Seu construtor envolve código que consome muitos recursos, ou
  • Injetar tem efeitos colaterais

Um bom exemplo disso são as sessões. Conseguir sessões através do ObjectManager é uma prática ruim, mas injetar uma classe de sessão como \Magento\Customer\Model\Sessionpoderia quebrar as coisas se sua classe fosse executada fora do escopo dessa sessão (digamos que você injete a sessão do cliente front-end em uma página de administrador). Você contorna isso injetando o proxy da sessão\Magento\Customer\Model\Session\Proxy e referenciando-o apenas quando você sabe que é válido. A menos que você faça referência a ela, a sessão nunca será instanciada e nada será interrompido.

No seu exemplo específico di.xml, parece que eles usaram o proxy para justificar a injeção de um controlador em vez da fábrica desse controlador. De qualquer maneira, não é para isso que os proxies devem ser usados, e o benefício dele nessa situação é provavelmente mínimo.

Ryan Hoerr
fonte
7

Os proxies gerados automaticamente do tipo Magento 2 podem ser usados ​​para "corrigir" erros de design. Isso pode ser muito útil. Existem 2 casos de uso:

  1. Envolva um gráfico de objeto caro que pode não ser necessário todas as vezes pelo dependente.

  2. Quebre uma dependência cíclica em que a classe Adepende Be a classe Bdepende um A.
    A injeção B\Proxyno Apermite instanciar A, que por sua vez pode ser usado para instanciar Bquando é realmente usado com o Aobjeto real .

Em caso de 1. a dependência que nem sempre é usada é um sinal de que a classe dependente faz muito, ou talvez faz muito por um método. O comando do console @ivan mencionado é um bom exemplo disso.

No caso de 2. Não conheço uma maneira genérica de acabar com essa dependência. Costumo reescrever se houver tempo, mas isso pode não ser uma opção.

Apenas como uma observação lateral, gostaria de acrescentar que existem muitos mais tipos de proxies no OOP do que a instanciação lenta gerada automaticamente que o Magento 2 usa (por exemplo, proxy remoto).

Vinai
fonte
Olá @ vinai, qual é a maneira de usar classes proxy via método __constructor () ou via di.xml.?
akgola
11
De acordo com as diretrizes de codificação do Magento, a seção 2.5 proxies NÃO DEVE ser declarada em construtores de classe. Os proxies DEVEM ser declarados em di.xml. Veja devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai
1

Aqui estão as respostas

por que uma classe proxy é usada nesse caso específico?

Se você examinar atentamente o código escrito para a classe "SetConversionValueObserver", se o Google adwards não estiver ativo "return" e se não houver pedido "return". Significa que o Objeto de coleta de pedidos será criado apenas quando os IDs de pedidos existirem e o Google AdWords ativo. se injetarmos a classe de coleção de pedidos real, o gerenciador de objetos criará o objeto de coleção com seus objetos de classe pai sem saber que o Google AdWords não está ativo e essa página de êxito do pedido será mais lenta. portanto, crie melhor o objeto sob demanda, que é o 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;
}

quando, em geral, alguém deve usar uma classe proxy? - Injetar classe Proxy quando você achar que a criação de objetos será cara e o construtor da classe consumir muito recursos. - quando você não deseja um impacto desnecessário no desempenho devido à criação do objeto. - quando você acha que a criação de objetos deve acontecer quando você chama um método específico em uma condição específica nem sempre. Por exemplo, o construtor Layout é um recurso intensivo.

Construtor de layout real 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;
}

Construtor proxy, dê uma olhada, nenhum construtor pai chamado e apenas passou o nome da classe de layout para que a criação real do objeto aconteça quando o método for chamado.

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

A classe proxy possui um método para criar objeto sob demanda, _subject é o objeto da classe passada.

/**
 * 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 método chamado usando _subject.

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