Magento 2 Design Patterns: ViewModel and Proxy

Magento 2 introduces a couple of interesting design patterns and solutions that make the code easier to read, better optimized and easier to work with. 

I will try to take you through some of them in this miniseries. For starters, let’s talk about ViewModels and Proxy. 

ViewModel

ViewModels have been introduced along with Magento 2.2 (which is an important note if you are making a module that needs to work with earlier versions) and are used in blocks. The beauty of this solution is that you don’t have to create separate block classes when you need a particular type of data in a template.

ViewModels allow separating a layer connected to displayed data from the rendering method, which is handled by the blocks. If you need to display, say, a products’ collection in a template, you can use ViewModel and a default Magento block class (it is  \Magento\Framework\View\Element\Template for the frontend and \Magento\Backend\Block\Template for the backend). 

If, in turn, you need more customized block rendering rules, you can create a new block. This way, you can create reusable code that is easier to read and compliant with SRP. 

Utilizing ViewModel requires two steps: 

  1. using a ViewModel class and 
  2. injecting an object in the layout configuration. 

1. Using ViewModel class

The ViewModel class has to implement the \Magento\Framework\View\Element\Block\ArgumentInterface interface. The interface doesn’t impose implementing any particular methods but it’s required to pass the ViewModel object to the block as an argument. While there is no requirement as to where you should place the class (all thanks to namespaces), it should, by convention, go to the ViewModel catalog.

A typical ViewModel could look like this: 

php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // file: app/code/Magently/Tutorial/ViewModel/ProductProvider.php <?php namespace Magently\Tutorial\ViewModel; use Magento\Framework\View\Element\Block\ArgumentInterface; class ProductProvider implements ArgumentInterface { public function get() { // your code here } }

Now that you have your class defined, you have to pass it as an argument in the Layout XML.

2. Injecting an object in the layout configuration

html
1 2 3 4 5 6 7 8 9 10 ... <referenceBlock name="block.name"> <block name="our.block.with.view.model" template="Magently_Tutorial::our/template.phtml"> <arguments> <argument name="view_model" xsi:type="object">Magently\Tutorial\ViewModel\ProductProvider</argument> </arguments> </block> </referenceBlock> ...

At this point, you can get the object in the template just as you’d normally get data from a block:

php
1 2 3 4 5 6 7 8 9 10 // file: app/code/Magently/Tutorial/view/frontend/templates/our/template.phtml <?php /** @var \Magento\Framework\View\Element\Template $block */ /** @var \Magently\Tutorial\ViewModel\ProductProvider $viewModel */ $viewModel = $block->getViewModel(); $products = $viewModel->get(); // your code here

Proxy

Proxy is another new feature in Magento. It is a set of classes, automatically generated by Magento on compile-time. Proxies are used to lazy load a class they extend. The resulting code contains a Proxy class that extends a class we need this Proxy for. 

Therefore, the Proxy class is a special one, as there are no objects of the extended class in its constructor and there is the Object Manager instead. The Proxy class contains the _getSubject() method that initializes/returns an object of the extended class only when a method is called. This means that upon injecting a Proxy object, it is light and takes up little resources. The object is instantiated only when it’s time for the code to use one of the methods.

It is best to use Proxy for classes with resource-intensive constructors (with lots of objects injected), e.g. session classes. 

As I mentioned before, each class can be used to create a Proxy just by adding \Proxy at the end. So, a Proxy for \Magento\Customer\Model\Session is \Magento\Customer\Model\Session\Proxy. Note, that the Proxy word is a class name in the namespace of a class it’s created for, unlike Factories, where Factory is just attached to the class name (e.g. Magento\Catalog\Model\ProductFactory).

Proxies should be injected with di.xml, as doing that directly in the constructor would limit our ability to overwrite an object. 

In our class, we use a standard class instead of a Proxy:

php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php namespace Magently\Tutorial\Model; use Magento\Customer\Model\Session; class SomeClass { /** * @var Session */ private $customerSesion; /** * SomeClass constructor. * @param Session $customerSession */ public function __construct( Session $customerSession ) { $this->customerSesion = $customerSession; } }

di.xml is where the magic happens:

html
1 2 3 4 5 <type name="Magently\Tutorial\Model\SomeClass"> <arguments> <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> </arguments> </type>

We inject Proxy for Customer Session in the class constructor. The example below illustrates the effect of the optimized code:

php
1 2 3 4 5 6 7 8 9 10 11 public function someMethod() { // do sth $sth = doSomething(); if ($sth) { // customerSession won't be initialized, the code is optimized return $sth; } return $this->customerSesion->getCustomerId(); }

If $sth returns true, the customerSession object will never be called. 

I do hope I managed to present these solutions in an accessible way. Please write your questions and remarks in the comments below and let me know what models would you like to see explored in the next articles!