Layout files can be a love/hate relationship. They are both powerful and complex. In this post I explore the difference between declarative and imperative styles of designing layout files (with a particular focus on Magento 2) and the pros and cons of the two approaches.
The View Element Tree
This blog is intended for those already familiar with layout files in Magento, but I will summarize the goal of layout files here for those less familiar.
Magento 2 generates the HTML for a page to display to a user from a tree of “view elements”. (These used to be called “structural elements”, but we rationalized the terminology.) There are two main types of view elements:
- Containers collect an ordered group of children view elements.
- Blocks can be used to generate dynamic content. Blocks can have named child view elements that are similar to arguments being passed in. (The ‘as’ attribute holds the child view element names for the parent block to reference them by.)
(Recently a third type of view element, a “UI Component”, was added in Magento 2. They are close enough to blocks for the purpose of this discussion – I don’t mention them further in this blog post.)
In Magento 1, blocks either had a fixed number of named children or an array. This has been split in Magento 2, originally with the idea of making a Visual Design Editor (VDE) easier to support. In Magento 2 if you want an array of view elements under a block, introduce a container under the block. (I should come clean at this point and say this is the intent. Not all blocks have been correctly updated yet – there may still be some blocks that still use an array of children without names. If you find one, this is the “old style” of designing a block.)
An example of a block having a child view element can be found when displaying categories. The layout file app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml contains
<block class="Magento\Catalog\Block\Product\ListProduct" … template="Magento_Catalog::product/list.phtml">
This block deals with the list of products, but it then uses a child block to render each individual product. The advantage of this approach is it makes it easier to replace the rendering of a product without having to also change the rendering of the list of products.
If you look in app/code/Magento/Catalog/view/frontend/templates/product/list.phtml you will find
<?php foreach ($_productCollection as $_product): ?>
which is iterating over each product in the collection to render it and merge it into the overall page.
So a web page is formed by asking the view element tree to render itself into HTML, where containers and blocks emit HTML that encloses their children appropriately. Blocks generally have a template file to contain the HTML to be emitted, allowing the HTML to be more easily customized without having to replace the PHP code used to implement the block as well.
What is Declarative vs Imperative?
Let’s start with some definitions. I will use the ones from Wikipedia.
- Declarative programming“expresses the logic of a computation without describing its control flow.”
- Imperative programming“describes computation in terms of statements that change a program ”
Declarative is generally considered “better”. The idea is you focus on saying the result, not how to get there. Imperative means you have to be more explicit about the changes you need to make to get to the end result. Declarative is generally considered better as it is generally easier for the user (they don’t have to say how to achieve the solution, just what they want achieved) and it gives the implementer more freedom to change the implementation strategy under the covers.
For example, SQL is declarative. You can say that you want to join the contents of two tables, but you don’t say how you want the join to be evaluated (scan tables, use indexes, do a sort-merge join, etc). The database server is free to choose between different implementation strategies, as long as the agreed result of the query is achieved. Writing PHP code with for loops to fetch records would be “imperative” – you are describing more precisely the steps to take in order to achieve the result.
Availability vs Merging
Rather than locking you in to a single implementation of functionality, Magento has been designed to be extensible. A key strength of Magento is the set of extensions available. But there are different approaches that an extension can take.
- An extension can make itself available for use, but not immediately weave itself into the user interface. Loading the extension is like adding it to the toolbox ready for use at some later time.
- An extension can merge itself directly and immediately into the user interface. The extension developer assumes they know best where the extension should be visible and provides those instructions with the extension to be automatically applied.
The second approach is more common with Magento extensions as it allows a non-technical Merchant to buy an extension, load it, and use it.
The first approach requires a site developer to reference the extension from the appropriate pages. It puts more obligation on the site developer, but also gives them more control over layout.
If an extension injects itself into where it feels appropriate in a page layout, but it’s not where a site developer wants it to be (possibly due to the look and feel the site developer is trying to achieve) then the site developer is responsible to move the location of the view elements introduced by the extension to their proper place in the page layout.
Layout Files are Imperative
This is where the declarative vs imperative style comes in. Magento 2 layout files are imperative in that they describe changes to make to a view element tree. An extension can describe how to add its blocks into the view element tree. A site developer can then define another module to move the added blocks to a new location. The view element tree is the “state” in the Wikipedia definition above. The order these instructions are executed in matters. Layout files are instructions to manipulate this tree.
For example, the <referenceContainer> and <referenceBlock> elements in a layout file identify a view element in the tree by name, and then make changes to the element.
- A common change to a container is to add a new block or container under the referenced container. The position of the new node is usually specified relative to existing nodes.
- A common change to a block is to add or replace a named child of the block.
Layout files are merged by applying from all modules all layout files for the target page in the order specified by module dependencies. (Any module can specify changes to apply to a view element tree for any page.) Layout files are also merged as a result of <update> elements in the middle of a layout file. The final view element tree structure is the result of applying all such changes.
Declarative Layouts in Magento
So what could a declarative layout approach look like in Magento?
Imagine instead of Magento coming with a set of pages all predefined, it presented itself as a suite of blocks where a site developer was responsible for assembling each page from scratch. This is actually quite a common approach for web site development. You use template files etc to keep the pages feeling consistent, but the web developer designs each page and controls exactly what goes onto the page.
In this mode of operation, a Visual Design Editor (VDE) makes a lot more sense. It may provide guidance on how to assemble a particular type of page, making suggestions of which blocks to put where. The developer would start with a blank slate and be able to build the page exactly as they wanted. Extensions would register new sets of blocks and other functionality for the VDE to provide in the palette to the site developer.
Which is Better?
For Magento, the imperative approach feels a better fit to me. It is a more complicated scheme in that extension and site developers have to understand how to build up multiple layout files to apply in order to achieve the desired final result. However given the complexity of some module integrations, this generally works out better. The extension developer works out how best to do the integration. For complicated extensions, this may involve changes to many different pages.
That is, while working out layout instructions is something a typical web developer does not have to deal with, providing layout files with extensions is probably less error prone than expecting a web developer to merge the large number of blocks that makes up a typical Magento site correctly. (Hence my usage of the name “site developer” rather than “web developer” in this blog post.) While it can be confusing, getting a site developer to move the occasional block in the wrong place is less work than building a complete page from scratch.
But I Really Want a Declarative Approach…
For those who really want a declarative style in Magento, fear not! You can actually do it today! While pages are normally built up from applying a series of layout files to the view element tree, you can define a new module that completely replaces all layouts for the page and start fresh. With this approach you can construct a new page from scratch from <container> and <block> elements without using any of the other layout instructions such as <move> or <referenceContainer>. The resultant layout file would be completely declarative – it would specify exactly where each container and block would be placed. There would be no imperative style layout instructions at all.
Generally I prefer more declarative approaches. I like that in SQL I specify the result I want and let the database engine decide the best way to achieve the results. But in Magento 2 I think the current imperative approach of layout files are the right approach.
Magento 2 layout files are more declarative than in Magento 1. For example, <referenceContainer> identifies which node in the tree to modify by name. Another layout could have moved this element from its original location in the tree. The <referenceContainer> element does not care where the referenced container is (for example, it does not specify the path to the node from the root of the view element tree for example). This makes it more declarative.
Magento 2 layout files are also moving away from <action> elements that let you call any method in the PHP code towards <argument> elements that provide data for the block to use. This is more declarative in the block can choose when and how to use the argument data. It is configuration for the block instead of <action> elements that trigger a function call (with side effects) in the code base.
But given the complexity of layout files and the view element tree, I suspect the current approach of layout files manipulating the view element tree allowing extension developers to automatically inject their extensions into the overall site is better than a style that requires a site developer to add the new functionality to web pages. Magento extensions are frequently a lot more complex than dragging a new “most recently posts” widget into the side bar of a WordPress page for example.
Having said that, it could be an interesting project to try and layer such an approach on top of Magento. That is, define a small number of region types and allow a site developer to drag and drop compatible widgets into those regions. There may be, for example, “top level” regions and “product” regions that can accept blocks that expect a product to be in context. That would provide a simple high level drag and drop model, while still retaining the more complex integration capabilities under the covers.