Question

So, I know theorically what is a proxy class in Magento 2. I've read the awesome Alan Storm article about it and I totally understand how those classes are generated.

However, and I don't know if it's because I'm a non native English speaker or if Alan's explanations are using non core classes which are very abstract, but I'm having a hard time understanding how it works and specially when to use it during development.

So let's take this example from the core 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>

I would like to know:

  • why a proxy class is used in that particular case ?
  • when, in general, should one use a proxy class ?
Was it helpful?

Solution

This particular usage is not a good example of using Proxy pattern. I think it is even useless in that particular piece of code, as a collection is not doing any DB operations unless load method is called. If their observer would be used in console command class as the dependency, then it makes sense to use a proxy.

The proxy class should only be used when during construction of the object you execute an expensive operation. A good example is Symfony console commands:

Imagine your console command is using ProductRepository as a dependency. Product repository constructor establishes MySQL connection to catalog database.

It means on every bin/magento call, no matter which command you execute, the repository dependencies will be instantiated. So the only way to avoid it is to use lazy instantiation of original object by creating a proxy. In this case database, connection to catalog database will be established only when you call a repository method.

Hope that helps to understand the idea of proxy better.

OTHER TIPS

A proxy class lets you dependency-inject a class that you won't necessarily need, and that has a high cost associated with doing so.

If you look at a proxy Magento has generated, like \Magento\Framework\View\Layout\Proxy, you'll see it has all of the same methods as the original class. The difference is that every time any of those are called, it checks if the class it's a proxy of has actually been instantiated, and creates the object if not. (This happens in a _getSubject() or _getCache() method.)

It's lazy loading for dependency injection.

You should use a proxy if a class dependency is not always used by your class, and:

  • Has a lot of dependencies of its own, or
  • Its constructor involves resource-intensive code, or
  • Injecting it has side effects

One good example of this is sessions. Getting sessions through the ObjectManager is bad practice, but injecting a session class like \Magento\Customer\Model\Session could break things if your class ever runs outside of that session's scope (say you inject the frontend customer session on an admin page). You get around that by injecting the session's proxy \Magento\Customer\Model\Session\Proxy instead, and only referencing it when you know it's valid. Unless you reference it, the session is never instantiated, and nothing breaks.

In your specific example of di.xml, it looks like they used the proxy to justify injecting a controller rather than that controller's factory. Either way, that is not what proxies are intended to be used for, and the benefit of it in that situation is likely minimal.

Magento 2 type autogenerated proxies can be used to "fix" design mistakes. That can be very handy. There are 2 use cases:

  1. Wrap an expensive object graph that might not be needed every time by the dependee.

  2. Break a cyclic dependency where class A depends on B and class B depends an A.
    Injecting B\Proxy into A lets you instantiate A, which then in turn can be used to instantiate B when it is actually used with the real A object.

In case of 1. the dependency that isn't always used is a sign that the dependee class does to much, or maybe does to much by one method. The console command @ivan mentioned are a good example of that.

In case of 2. I don't know a generic way to break up that dependency. I tend to rewrite if there is time, but that might not be an option.

Just as a side note, I would like to add that there are many more types of proxies in OOP than the autogenerated lazy instantiation one Magento 2 uses (e.g. remote proxy).

Here are the answers

why a proxy class is used in that particular case ?

If you take a close look below code which is written for class "SetConversionValueObserver", if Google adwards is not active "return" and if there is no order "return". Means, Order Collection Object will be created only when order Ids exist and Google adwords active. if we inject actual Order collection class then object manager create collection object with its parent class objects without knowing Google adwords not active and that slow down order success page. so, better create object on demand that is the use of 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;
}

when, in general, should one use a proxy class ? - Inject Proxy class when you feel object creation will be expensive and class’s constructor is particularly resource-intensive. - when you don't want unnecessary performance impact due to object creation. - when you feel object creation should happen when you call particular method in particular condition not always. For example Layout constructor is a resource-intensive.

Actual 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, take a look, no parent constructor called as well as just passed layout class name so that actual object creation happen when method called.

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

Proxy class has method to create object on demand, _subject is the object of passed class.

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

And method called using _subject.

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top