I’m starting to get a handle on how Magento 2 renders a layout and how those changes will impact folks to spend their day in layout handle XML files. This one’s mainly for myself – posting it here to get it out of my head.
The first step of layout rendering in Magento 2 is still building a “package layout” – although it might be better to call it a “global handle merge XML tree”. Magento will read through every layout file for an area in the module folder, and theme folders. Theme XML files are combined with their module file counterparts, unless placed in an override folder. Also, this isn’t a simple concatenation – each individual file corresponds to a top level
<handle="handle_name"> <!-- contents of a single `handle_name.xml` here --> </handle>
The second step of layout rendering is looking at the specific handles for a request, and plucking out matching handles from the global handle merge XML tree. These plucked nodes are then combined into single global tree – sans the top level
<handle/> nodes. This corresponds to the page layout XML tree in Magento 1 – although it would probably be better to call this the “request layout XML tree” in Magento 2, since page layout usually refers to a Magento 2 PageLayout object, or the specific
page_layout attribute/XML file type (
So far we’re in familiar territory – but this is where Magento 2 goes off on its own. In Magento 1, Magento core code would loop recursively loops through this the reduced XML tree and either instantiate a block and call action methods on existing blocks to build a nested tree of block objects. Then, Magento would call the root block’s
toHtml object and render the block.
Magento 2’s processing is more complicated. First off, it’s three stage
- A reading, or scheduling stage
- A generating stage
- A rendering stage
In the reading stage, Magento will iterate over the top level nodes of the reduced XML tree. Magento will pass each top level XML node to the Reader objects in a reader pool. Each Reader object has the responsibility of telling a scheduler object what needs to happen in order to render the layout. Put another way, each reader objects schedules certain events to happen (create this block, move this block). Each reader objects is restricted to operating on a certain set of tags – the “move” reader will only operate on
<move/> tags. Some reader objects, like the “block” reader, will traverse recursively deeper into the XML tree to find more tags to process.
Here’s a (partial?) list of readers – readers are configured on a reader pool via array dependency injection, but each reader type can support multiple tags, so additional readers may be instantiated during a request.
MagentoFrameworkViewLayoutReaderContainer– operates on
MagentoFrameworkViewLayoutReaderBlock– operates on
MagentoFrameworkViewLayoutReaderUiComponent– operates on
MagentoFrameworkViewLayoutReaderMove– operates on
Once the reading/scheduling stage is done, the generating stage begins. The generating stage is where our tree of nested block objects are generated. There is a pool of generator objects. The generator pool object will build a structure object based on information in the scheduler object. The structure object keeps track of container and block parent/child relationships. Then, the generator pool object will run through a list of generator objects. Each generator object has a specific type of object it needs to generate (such as blocks), or set of tasks it needs to perform on the structure object. Each generater object looks at the scheduler object to find out which tasks it needs to accomplish.
This is a list of generators used during the rendering of the product information page. Generators are configured on the pool via array dependency injection. It also appears something in the Block generator causes each generator to be re-called – it’s not 100% clear what causes this.
MagentoFrameworkViewPageConfigGeneratorHead MagentoFrameworkViewPageConfigGeneratorBody MagentoFrameworkViewLayoutGeneratorBlock MagentoFrameworkViewLayoutGeneratorContainer MagentoFrameworkViewLayoutGeneratorUiComponent
Once generation is complete, a nested tree of block objects will exist. In the final rendering stage, Magento will automatically render each top level container – i.e. a container that has no parents – extra i.e. each “root” container.
There’s still a few open questions I have that I haven’t had time to research – specifically what triggers the second call to the generator pool, and which readers and generators split the
<body/> sections out and how is that context tracked. It’s also not clear where
remove= blocks are handled, and how that special root template figures into things.
Handle order is still important, as it creates layout update that are processed in a certain order. However, when these updates are read, each different node is scheduled in a certain order, which will have an impact when the generators start their generating. It’s also important to note that container rendering and block rendering are separate items, which means no
action method calls on containers.
Still lots to research here.