WTF is a proxy in Magento?

Magento 2, Magento Proxy, Coding tips, di.xml

When navigating Magento source code or when using the Magento Coding Standard you have come across them: Proxies. But what are they exactly and when should you use them?

VPN? Obscure website?

Proxies are most known to get around firewalls so you can open websites you normally aren't allowed to open. Some people use it to view videos on Netflix that are not available in their country, other people use it to pass the Great Firewall of China for example.

In Magento it works a little bit different: A proxy is there used to defer the loading of the actual object until it is actually required. This is useful when you have code that can be slow, by using a proxy the slow code only will be executed when it actually is required, and not before.

Magento handles proxying by creating an intermediate class that extends the target class and copies all the methods, it's comparable to the way that the Interceptors for plugins are generated.

Within these methods, the _getSubject() method is called, which initiates the target object. This looks like this:

/**
* Get proxied instance
*
* @return \Magento\Customer\Model\Url
*/
protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get($this->_instanceName)
            : $this->_objectManager->create($this->_instanceName);
    }
    return $this->_subject;
}

This is an example of the \Magento\Customer\Model\Url class, but this piece of code is for every class the same except for the type-hint.

MageTested.com (ad)

Do you want your Magento store to be more reliable? Tired of things that suddenly break the checkout process without anyone noticing? You can hire my services to kickstart End-2-End testing for your Magento store. This way you know for sure that your store is behaving as expected before releasing that new feature or that update.

View MageTested.com for more information.

The ObjectManager is evil and should never be used!1!!!!!11!

I hear you. We are learned that you should never use the Object Manager directly, but here Magento is giving a bad example. But Magento is using it here in a valid way. I'll even go one step further: This is the only way.

In this case, using the Object Manager is the only to initiate the target class. In any normal case, you would type-hint the class in your constructor, but that would mean that the class is initiated immediately. The class could be initiated by using new TargetClass($dependency1, $dependency2) as it is auto-generated anyway, but then you would lose the ability to use plugins on the target class, that's something we don't want either.

So, from my point of perspective, this use case of the Object Manager is perfectly valid.

How do I use proxies myself?

Simple, just append \Proxy to your class name, \Vendor\Module\Model\Posts becomes \Vendor\Module\Model\Posts\Proxy. That's it. Next.

Joking aside, that's how you can actually a proxy. The downside of this method is that your IDE can't autocomplete the rest of the code as it does not exists yet. You first have to run this code so the proxy class gets generated and your IDE has to index the file.

But there is another way. It involves a bit more work but it's neater: Using the di.xml to inject the proxy. 

Say you want to use the \Magento\Checkout\Model\Session class for example, your constructor would look like this:

namespace Vendor\Module\Custom\Supercool;

class DiExample
{
    /**
     * @var \Magento\Checkout\Model\Session
     **/
    private $session;

    public function __construct(
        \Magento\Checkout\Model\Session $session
    ) {
        $this->session = $session;
    }

    // ...
}

Then you need to add this in your di.xml:

<type name="Vendor\Module\Customer\Supercool">
    <arguments>
        <argument name="session" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument>
    </arguments>
</type>

And that's it. Would you now dump the class name in the constructor then you would see that it's not the requested class but the proxy variant:

namespace Vendor\Module\Custom\Supercool;

class DiExample
{
    /**
     * @var \Magento\Checkout\Model\Session
     **/
    private $session;

    public function __construct(
        \Magento\Checkout\Model\Session $session
    ) {
         // Will print \Magento\Checkout\Model\Session\Proxy
    var_dump(get_class($session));
        $this->session = $session;
    }

    // ...
}

It is good to note that although you can inject the class directly, the better approach is to use the di.xml variant. I wasn't sure myself why you should do that so I asked Twitter and got this response which explains it all:

When should I use proxies?

That's the real question here. Magento itself uses it at a few different places, most people will encounter them when using Sessions: They are considered slow so it's a good idea to put them behind a proxy.

But Magento uses them at a whole lot of other places: Do a search in a Magento store on "\Proxy" and set the file mask on "di.xml". You will find proxies in places that use caching, the event manager, database resources, and many more.

It speaks for itself that you should use proxies at places that Magento itself uses proxies, but you should use proxies also at places where you do extensive operations in the constructor. Why? Because the constructor is always executed when you require that specific class in another class.

Also, it is recommended to use proxies for your commands if you are not sure about the dependencies it uses as the constructor is executed at every bin/magento command:

Want to respond?