Instead of extending upon Block classes, ViewModels are forming a new best practice for dealing with adding custom logic to existing templates in Magento 2. Very cool. But unfortunately, this is a bit harder when it comes to widgets. Harder, but only slightly, because of plugins.

How ViewModels work

Most Blocks are initialized via the XML layout, so that you can add in arguments like strings, booleans, even arrays. Every time such an argument is added to a Block class, it becomes part of the internal $data array of the Block, which leads to magic methods as well: If you add an argument foo with value bar, you can call the blocks method getFoo() to get this value out again.

What is then a ViewModel? It is actually just one of those arguments that can be added to Block, but now the argument being an object. The only requirement of such an object, commonly referred to as a ViewModel, is that it implements an ArgumentInterface.

Thanks to ViewModels, it has become possible to get rid of block overrides - extending a block class with a child class and then needing to extend its complex constructor as well. With ViewModels, we can prefer composition over inheritance.

How widgets work (and why ViewModels are an issue)

Widgets can be configured via the admin, amongst other means. For instance, you can assign a CMS Widget to a specific container or you can add it to content using a WYSIWYG editor. When editing a widget, a widget is able to include parameters (defined in a widget.xml file) that generates a form.

A widget is in essence a block. However, it does not receive its data via XML layout arguments. Instead, the $data array is based on the widget form values. This is important to know, because in the previous section on ViewModels, we inserted the ViewModel into the Block by using XML arguments that are not available in a widget.

assign() the ViewModel anyway

This does not mean that you can not use ViewModels with widgets. It simply means that you can't use the XML layout for this. One solution I've been using is to use the assign() method in a Block class.

Within the PHTML template, there are variables available: $block refers to the Block object, $this refers to the template engine. But there could be other variables as well, as long as they are assigned within the Block class before rendering the template. For instance, you can assign a new variable $foo for use within the PHTML template, by overriding to toHtml() method of a class:

public function toHtml()
{
	$this->assign('foo', 'bar');
	return parent::toHtml();
}

Plugins to the rescue

Now, the whole point was to prevent overriding Block classes, because composition feels much better. This is where Plugin classes come into play. We can intercept the original toHtml() method of the original Block class, by using a plugin method beforeToHtml(). And thanks to the assign() method, we can now add any variable to the PHTML template that we like.

First, we define the XML to intercept a target class (type) with our plugin:

<type name="Yireo\ExampleAssignToBlock\Block\Example">
    <plugin name="yireo_example_assign_variable_to_block" type="Yireo\ExampleAssignToBlock\Plugin\AssignVariableToBlock"/>
</type>

And next, the Plugin class adds a new variable via the original toHtml():

public function beforeToHtml(ExampleBlock $exampleBlock)
{
    $exampleBlock->assign('viewModel', $this->exampleViewModel);
    return [];
}

The code above assumes that the ViewModel was injected as well into the Plugin class. For the full example module, check https://github.com/yireo-training/magento2-example-assign-to-block

In short, it is perfectly possible to add ViewModels to widgets, as long as you realize that the XML layout does not deal with this (in most cases). Plugins allow you to get the job done.

Posted on September 7, 2020

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We schrijven niet te commerciële dingen, we richten ons op de technologie (waar we dol op zijn) en we komen regelmatig met innovatieve oplossingen. Via onze nieuwsbrief kun je op de hoogte blijven van al deze coolness. Inschrijven kost maar een paar seconden.