Magento 2:什么是代理类的实际解释?


17

因此,我从理论上知道Magento 2中的代理类是什么。我已经阅读了有关Alan Storm的精彩文章,并且我完全理解了这些类是如何生成的。

但是,我不知道这是因为我不是英语母语人士,还是Alan的解释是否使用了非常抽象的非核心类,但是我很难理解它的工作原理,特别是何时使用在开发过程中。

因此,让我们以以下示例为核心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>

我想知道:

  • 为什么在这种特定情况下使用代理类
  • 通常,什么时候应该使用代理类?

Answers:


17

这种特殊用法不是使用代理模式的好例子。我认为在特定的代码段中它甚至没有用,因为除非调用load方法,否则集合不会执行任何DB操作。如果在控制台命令类中将其观察者用作依赖项,则使用代理是有意义的。

仅当在构造对象期间执行昂贵的操作时才使用代理类。一个很好的例子是Symfony控制台命令:

想象一下,您的控制台命令使用ProductRepository作为依赖项。产品存储库构造函数建立与目录数据库的MySQL连接。

这意味着在每次bin/magento调用时,无论您执行哪个命令,都将实例化存储库依赖关系。因此,避免这种情况的唯一方法是通过创建代理使用原始对象的惰性实例化。在这种情况下,仅当您调用存储库方法时,才会建立与目录数据库的连接。

希望有助于更好地理解代理的思想。


1
我选择的示例毫无用处,这让我更加困惑。从理论上来说,我再次理解这个概念。但是我不明白:为什么不将ProductRepository作为依赖项添加到控制台命令中,如果不对每个命令都使用它。它不应该仅是您所使用命令的依赖项吗?根据您所说的,代理是一种“跳过”依赖关系的方法吗?但是在那种情况下,为什么首先要依赖呢?
拉斐尔(Raphael)在Digital Pianism上,2013年

1
我认为Symfony控制台命令是一个很好的示例,因为您必须从中与Magento对话,而这样做的唯一方法是在构造函数中指定依赖项。在Symfony控制台组件中,您必须为每个命令创建一个类。此类具有configure和execute方法。Configure设置其名称和参数,而execute实际上执行昂贵的操作。如果在configure上执行了昂贵的操作,那么您就不胜其烦了,这就是为什么代理是解决此问题的原因。
伊万·谢普尔尼

13

代理类使您可以依赖注入不需要的类,并且这样做的成本很高。

如果您查看Magento生成的代理(如)\Magento\Framework\View\Layout\Proxy,则会发现它具有与原始类相同的所有方法。不同之处在于,每次调用它们中的任何一个时,它都会检查是否实际上实例化了作为其代理的类,如果没有,则创建该对象。(这发生在_getSubject()_getCache()方法中。)

依赖注入的延迟加载。

如果您的类并不总是使用类依赖关系,则应使用代理,并且:

  • 有很多依赖关系,或者
  • 其构造函数涉及资源密集型代码,或者
  • 注射有副作用

会议就是一个很好的例子。通过ObjectManager获取会话是不好的做法,但是\Magento\Customer\Model\Session如果您的类在该会话的范围之外运行,则注入会话类可能会破坏事情(例如,您在管理页面上注入了前端客户会话)。您可以通过注入会话的代理来解决此问题\Magento\Customer\Model\Session\Proxy来,并且仅在知道其有效时才引用它。除非您引用它,否则会话永远不会实例化,并且不会中断。

在您的的特定示例中di.xml,似乎他们使用代理来证明注入控制器而不是该控制器的工厂是合理的。无论哪种方式,这都不是代理要使用的目的,在那种情况下它的好处可能很小。


7

Magento 2类型的自动生成的代理可以用来“修复”设计错误。那可能非常方便。有2个用例:

  1. 包装一个昂贵的对象图,该依赖图可能不需要受抚养者每次都需要。

  2. 打破class A依赖B和class B依赖于的循环依赖A
    注射B\ProxyA可以实例A,然后又可以用于实例化B时,它实际上是与真实的使用A对象。

如果是1。的不经常使用的依赖项表明被依赖者类做了很多事情,或者可能通过一种方法做了很多事情。提到的控制台命令@ivan是一个很好的例子。

在的情况下,2我不知道一个通用的方法来打破这种依赖性。如果有时间,我倾向于重写,但这可能不是一个选择。

顺便提一句,我想补充一点是,与Magento 2使用的自动生成的惰性实例(例如,远程代理)相比,OOP中的代理类型更多。


您好@vinai,通过__constructor()方法或di.xml使用代理类的方式是什么?
akgola

1
根据Magento编码指南的第2.5节,不得在类构造函数中声明代理。代理必须在di.xml中声明。参见devdocs.magento.com/guides/v2.3/coding-standards/…–
维奈

1

这是答案

为什么在这种特定情况下使用代理类?

如果您仔细阅读下面为类“ SetConversionValueObserver”编写的代码,如果Google adwards未激活,则“返回”,如果没有订单,则“返回”。意味着,仅当订单ID存在且Google adwords处于活动状态时,才会创建订单集合对象。如果我们注入实际的Order集合类,则对象管理器会使用其父类对象创建集合对象,而不会知道Google adwords未处于活动状态,并且会降低订单成功页面的速度。因此,更好地按需创建使用代理的对象。/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;
}

通常,什么时候应该使用代理类? -当您觉得创建对象会很昂贵并且类的构造函数特别耗费资源时,请注入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
 *
 * @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;
}

代理类具有按需创建对象的方法,_subject是传递的类的对象。

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

和使用_subject调用的方法。

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.