Magento 2: explication pratique de ce qu'est une classe proxy?


17

Donc, je sais théoriquement ce qu'est une classe proxy dans Magento 2. J'ai lu le génial article d'Alan Storm à ce sujet et je comprends parfaitement comment ces classes sont générées.

Cependant, et je ne sais pas si c'est parce que je ne parle pas anglais ou si les explications d'Alan utilisent des classes non fondamentales qui sont très abstraites, mais j'ai du mal à comprendre comment cela fonctionne et surtout quand utiliser pendant le développement.

Prenons donc cet exemple du noyau dans 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>

J'aimerais savoir:

  • pourquoi une classe proxy est utilisée dans ce cas particulier?
  • quand, en général, faut-il utiliser une classe proxy?

Réponses:


17

Cette utilisation particulière n'est pas un bon exemple d'utilisation du modèle de proxy. Je pense que cela est même inutile dans ce morceau de code particulier, car une collection ne fait aucune opération de base de données à moins que la méthode de chargement ne soit appelée. Si leur observateur est utilisé dans la classe de commandes de la console comme dépendance, il est alors judicieux d'utiliser un proxy.

La classe proxy ne doit être utilisée que lorsque, pendant la construction de l'objet, vous exécutez une opération coûteuse. Un bon exemple est les commandes de la console Symfony:

Imaginez que votre commande de console utilise ProductRepository comme dépendance. Le constructeur du référentiel produit établit une connexion MySQL à la base de données du catalogue.

Cela signifie qu'à chaque bin/magentoappel, quelle que soit la commande que vous exécutez, les dépendances du référentiel seront instanciées. Donc, la seule façon de l'éviter est d'utiliser l'instanciation paresseuse de l'objet d'origine en créant un proxy. Dans ce cas, la connexion à la base de données du catalogue sera établie uniquement lorsque vous appelez une méthode de référentiel.

J'espère que cela aide à mieux comprendre l'idée de proxy.


1
Le fait que l'exemple que j'ai choisi soit inutile m'a rendu encore plus confus. Encore une fois, théoriquement, je comprends le concept. Mais ce que je ne comprends pas: pourquoi ajouteriez-vous ProductRepository en tant que dépendance à la commande de console si vous ne l'utilisez pas pour chaque commande. Cela ne devrait-il pas être une dépendance uniquement pour les commandes que vous utilisez? Selon ce que vous avez dit, le proxy est un moyen de "sauter" une dépendance? Mais dans ce cas, pourquoi est-ce une dépendance en premier lieu?
Raphael au Digital Pianism du

1
Je pense que la commande de la console Symfony est un excellent exemple, car vous devez en parler à Magento, et la seule façon de le faire est de spécifier une dépendance dans le constructeur. Dans le composant de console Symfony, vous devez créer une classe pour chaque commande unique. Cette classe a des méthodes de configuration et d'exécution. Configure définit son nom et ses arguments, tandis que l'exécution exécute en fait une opération coûteuse. Si une opération coûteuse est exécutée sur configure, alors que vous êtes foutu, c'est pourquoi le proxy est la réponse à ce problème.
Ivan Chepurnyi

13

Une classe proxy vous permet d'injecter en dépendance une classe dont vous n'avez pas nécessairement besoin, et qui a un coût élevé associé à le faire.

Si vous regardez un proxy que Magento a généré, \Magento\Framework\View\Layout\Proxyvous verrez qu'il a toutes les mêmes méthodes que la classe d'origine. La différence est que chaque fois que l'un de ceux-ci est appelé, il vérifie si la classe dont il est un proxy a réellement été instancié et crée l'objet sinon. (Cela se produit dans une méthode _getSubject()ou _getCache().)

C'est un chargement paresseux pour l'injection de dépendances.

Vous devez utiliser un proxy si une dépendance de classe n'est pas toujours utilisée par votre classe et:

  • A beaucoup de dépendances qui lui sont propres, ou
  • Son constructeur implique du code gourmand en ressources, ou
  • L'injection a des effets secondaires

Un bon exemple de ceci est les sessions. Obtenir des sessions via l'ObjectManager est une mauvaise pratique, mais injecter une classe de session comme \Magento\Customer\Model\Sessionpourrait casser des choses si votre classe s'exécute jamais en dehors de la portée de cette session (par exemple, vous injectez la session client frontale sur une page d'administration). Vous pouvez contourner cela en injectant le proxy de la session à la \Magento\Customer\Model\Session\Proxyplace et en le référençant uniquement lorsque vous savez qu'il est valide. Sauf si vous y faites référence, la session n'est jamais instanciée et rien ne se casse.

Dans votre exemple spécifique de di.xml, il semble qu'ils aient utilisé le proxy pour justifier l'injection d'un contrôleur plutôt que la fabrique de ce contrôleur. Quoi qu'il en soit, ce n'est pas à cela que les procurations sont destinées et le bénéfice de celui-ci dans cette situation est probablement minime.


7

Les proxys autogénérés de type Magento 2 peuvent être utilisés pour "corriger" les erreurs de conception. Cela peut être très pratique. Il existe 2 cas d'utilisation:

  1. Enveloppez un graphique d'objet coûteux qui pourrait ne pas être nécessaire à chaque fois par la personne à charge.

  2. Briser une dépendance cyclique où la classe Adépend Bet la classe Bdépend d'un A.
    L'injection B\Proxydans Avous permet d'instancier A, qui à son tour peut être utilisé pour instancier Bquand il est réellement utilisé avec l' Aobjet réel .

Dans le cas de 1. la dépendance qui n'est pas toujours utilisée est un signe que la classe dépendante fait trop, ou peut-être beaucoup par une méthode. La commande de console @ivan mentionnée en est un bon exemple.

Dans le cas de 2. Je ne connais pas de moyen générique de briser cette dépendance. J'ai tendance à réécrire s'il reste du temps, mais ce n'est peut-être pas une option.

Juste comme note latérale, je voudrais ajouter qu'il existe beaucoup plus de types de proxy dans la POO que l'instanciation paresseuse générée automatiquement que Magento 2 utilise (par exemple, un proxy distant).


Bonjour @ vinai, comment utiliser les classes proxy via la méthode __constructor () ou via di.xml.?
akgola

1
Selon les directives de codage de Magento, la section 2.5 ne doit PAS être déclarée dans les constructeurs de classe. Les proxys DOIVENT être déclarés en di.xml. Voir devdocs.magento.com/guides/v2.3/coding-standards/…
Vinai

1

Voici les réponses

pourquoi une classe proxy est utilisée dans ce cas particulier?

Si vous regardez attentivement le code ci-dessous qui est écrit pour la classe "SetConversionValueObserver", si Google Adwards n'est pas actif "retour" et s'il n'y a pas d'ordre "retour". Moyens, l'objet de collecte de commandes ne sera créé que lorsque les identifiants de commande existent et que les adwords Google sont actifs. si nous injectons la classe de collection Order réelle, le gestionnaire d'objets crée un objet de collection avec ses objets de classe parent sans savoir que Google AdWords n'est pas actif et ralentit la page de réussite de la commande. ainsi, mieux créer un objet à la demande qui est l'utilisation d'un 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;
}

quand, en général, faut-il utiliser une classe proxy? - Injectez la classe Proxy lorsque vous pensez que la création d'objets sera coûteuse et que le constructeur de la classe est particulièrement gourmand en ressources. - lorsque vous ne voulez pas d'impact inutile sur les performances en raison de la création d'objets. - lorsque vous sentez que la création d'objet doit se produire lorsque vous appelez une méthode particulière dans une condition particulière, pas toujours. Par exemple, le constructeur de mise en page est gourmand en ressources.

Constructeur de mise en page réel vs mise en page / 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;
}

Constructeur proxy, jetez un oeil, aucun constructeur parent appelé ainsi que le nom de classe de disposition juste passé afin que la création d'objet réelle se produise lors de l'appel de la méthode.

 /**
 * 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 a une méthode pour créer un objet à la demande, _subject est l'objet de la classe passée.

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

Et la méthode appelée using _subject.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.