This article is part of a series on design patterns for framework independent code, especially to write extensions for Magento 1 and Magento 2 alike.

All Parts:

Every application has its own means to render a HTML page. There are template engines with include mechanisms, HMVC, and of course the XML based two step view of Magento with its layout and blocks. Somehow we need to use them if we want to display anything in our extension.

Can we abstract it? Possibly. Is it worth the effort? I’d argue “no”. MVP (Model View Presenter) comes to mind, as it was used in GWT (Google Web Toolkit), but this was a framework that tried to disguise web applications as desktop applications and I don’t want to go down that road again.

However, the exact presentation is not part of the business logic that we want to have decoupled. But what we can – and should – do, is to extract everything from templates, block classes and the like that is not dealing with presentation details.

View Models

In fact I want them to only receive the data they need and avoid giving them access to anything that can cause side effects. You don’t save models from within a template, right?

For this purpose, I introduced View Models. I took the term from the Model View ViewModel pattern (MVVM) which splits the classic “view” into “view” (renders) and “view model” (contains UI logic and data). However, I am not going to implement this pattern. It makes sense in desktop applications or in JavaScript based web applications (Angular.js and Knockout.js use it), but not for web backend, i.e. PHP. As Anthony Ferrara stated recently, and I wholeheartedly agree:

Here’s my “beginners guide to MVC for the web”:

Lesson 1

You don’t need “MVC”.
Anthony Ferrara on “MVC on the web”

Read the whole post if you don’t know it yet, and also the second part that talks about MVC’s “siblings”.

So what do my View Models actually do? In the simplest form, they are just data objects. For example, I have a CategorySuggestion class that presents a suggested category based on what you typed (in the autosuggest preview):

The block receives this instead of a Category instance, because

  • it does not care about other aspects of the category
  • the “num_results” (results for a given search term within the category) is semantically not part of the Category itself, but here we need it

Can you tell that I am not a friend of using arrays as structs? This is again has something to do with explicitness in code.

You could say that view models do not represent data as it is stored, but data as it needs to be displayed. Some of my view models even contain a $cssClasses property, though this would probably be better refactored into something more semantical.

Responsibility of my View Models:

  • hold data that needs to be displayed
  • allow querying this data for output related properties

What they are not responsible for:

  • rendering output (no HTML!)
  • operate on the business layer (they should not trigger database queries themselves, only contain their results)

This is similar to what block classes are supposed to do in Magento, but with stricter boundaries. In Magento, blocks pull the information they need from models; in my architecture, the business layer pushes this information to them in form of view models. More on that later on.

View Helpers

Something else that you can probably extract into the library are “view helpers”. I know, “helper” does not mean much, and I’ve said it myself:

In general, having classes named Helper, Util or similar just says “I have some functions that I don’t know where to put” and don’t make much sense as a class.

But you probably know the term for methods that are used to modify output data and I’ve been guilty to use it myself more often than not.

For example, in the Solr extension, the autosuggest blocks had a method named highlight() which added <span class="highlight"> around the search term in results.

I extracted this functionality into a HtmlStringHighlighter class which implements this simple interface:

The blocks that need it now can retrieve or instantiate this default highlighter but are still free to replace the concrete implementation. Putting the HTML with highlight added into the View Model would have robbed us of this flexibility. Remember, the view model is not responsible for rendering output.

Push instead of Pull

As stated above, I prefer to push data to the presentation layer instead of it being pulled. The old version of our Solr extension did it the Magento way and requested search results from the block classes. Of course, the resulting collection and other data was saved to properties to prevent requesting them twice, and there certainly is an advantage on this kind of lazy loading: You only load data if you really display it. So if the filter block has been removed from the layout, we don’t aggregate the filter data.

But there is also a great disadvantage: you don’t have full control over the order of operations, i.e. what happens when.

This already has been a source of bugs. I was once faced with a case where results were loaded before all filters were applied, since the filter block was loaded after the pagination block. The pagination block triggered loading the results, but the filter block applied the filters.

So how do we turn this around? Here is a simplified example for a Magento 1 controller:

We load the layout first to instantiate all the blocks (which also initializes the layered navigation in this case), then make a search request and pass the results to the block(s) that renders it, then actually start rendering the page.

Another option, especially if the blocks need different data, is to pass them to the library as well. The blocks could implement this interface, provided by the library:

The SolrRequest in our library could then get another method:

And the code from above would be changed to:

Now pushing the data to the view is also the responsibility of the independent library, we just need to pass it to the views.

The next article will address a problem that mainly occurs in Magento 1 which follows some outdated standards: Using Advanced Autoloading

Fabian Schmengler

Author: Fabian Schmengler

Fabian Schmengler is Magento developer and trainer at integer_net. His focus lies in backend development, conceptual design and test automation.

Fabian was repeatedly selected as a Magento Master in 2017 and 2018 based on his engagements, active participation on StackExchange and contributions to the Magento 2 core.

More Information · Twitter · GitHub