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.
About the author
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.