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.
Hey, I'm running a developer focussed newsletter called MageDispatch.com, which is for the community and by the community. Here you can share links that you think that the community should know about. If you like this kind of technical content you will like this newsletter too.
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:
DI compile command needs to generate all code, including proxies, at the deployment phase. It uses declarations in di.xml to determine which proxies to generate. It does not look at the PHP source code. Proxy declared in constructor won’t be generated and will crash in prod mode.
— Sergii Shymko (@SergiiShymko) September 25, 2020
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:
Fun fact: This is exactly the same in Magento, the constructor for every command gets executed when you run *any* `bin/magento` command. If you have a dependency on a class that executes something in the constructor, you can use a proxy class to prevent that. https://t.co/TnRORN9JWd
— Michiel Gerritsen (@mbdgerritsen) September 24, 2020
Want to respond?