Over in my Patreon slack, a patron came to me with the following layout handle XML chunk.
This block of XML would successfully remove the block named breadcrumbs
, but it would not successfully remove the block named product_list_toolbar
.
This is one of those places where Magento’s lack of documentation and/or a cogent systems philosophy really bites practitioners in the butt. Even as an experienced M2 developer, my mind came up with multiple possible causes
- That was
<remove/>
in M1, will<remove/>
work in M2? - Is
remove=true
something that works by coincidence but isn’t supported? - Is
remove=true
something the works for only blocks or works for only containers? (you can usereferenceBlock
andreferenceContainer
interchangeably in Magento’s core) - Is handle loading order to blame here?
Worse – because Magento 2’s layout loading is much more complicated that it was in M1, even someone like me isn’t sure where to start debugging. There are too many layers where the remove
instruction might get, um, removed. This makes it impossible to point a user-programmer to the right place for some debugging code.
Fortunately, my patron was also a Commerce Bug user – using the Layout tab’s View Page/Request
button we were able to view the final layout XML Magento uses to instantiated blocks. We examined the layout loading order, and searched for conflicting rules. Everything seemed fine – except we noticed a small bit of weirdness in the initial product_list_toolbar
code
#File: vendor/magento/module-catalog/view/frontend/layout/catalog_category_view.xml
<block class="MagentoCatalogBlockProductListProduct" name="category.products.list" as="product_list" template="Magento_Catalog::product/list.phtml">
<!-- ... -->
<block class="MagentoCatalogBlockProductProductListToolbar" name="product_list_toolbar" template="Magento_Catalog::product/list/toolbar.phtml">
<block class="MagentoThemeBlockHtmlPager" name="product_list_toolbar_pager"/>
</block>
<action method="setToolbarBlockName">
<argument name="name" xsi:type="string">product_list_toolbar</argument>
</action>
</block>
For reasons that weren’t immediately apparent, Magento’s core code calls the setToolbarBlockName
method on the parent MagentoCatalogBlockProductListProduct
block. This was curious enough to warrant an investigation of what was happening in that parent block’s source. This, in turn, revealed the problem.
For reasons known only to those behind the wall of corporate and historic fog, the parent block object does some weird monkey business with the toolbar in the _beforeHtml
method (called right before the block’s HTML renders)
#File: vendor/magento/module-catalog/Block/Product/ListProduct.php
protected function _beforeToHtml()
{
$toolbar = $this->getToolbarBlock();
/* ... monkey business ... */
$this->setChild('toolbar', $toolbar);
/* more monkey business */
return parent::_beforeToHtml();
}
By itself, this code isn’t damning – but if we look at the getToolbarBlock
method
#File: vendor/magento/module-catalog/Block/Product/ListProduct.php
public function getToolbarBlock()
{
$blockName = $this->getToolbarBlockName();
if ($blockName) {
$block = $this->getLayout()->getBlock($blockName);
if ($block) {
return $block;
}
}
$block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, uniqid(microtime()));
return $block;
}
We see that, if Magento can’t fetch toolbar block, it instantiates a new one via code.
#File: vendor/magento/module-catalog/Block/Product/ListProduct.php
$block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, uniqid(microtime()));
For my Patron? This meant their remove=true
attribute was working. The product_list_toolbar
block was removed from the layout. However, during render, Magento just re-instantiated the toolbar block and stuck it back in the layout.
This sort of thing isn’t exactly new in Magento – in fact this specific problem goes back to Magento 1
#File: app/code/core/Mage/Catalog/Block/Product/List.php
public function getToolbarBlock()
{
if ($blockName = $this->getToolbarBlockName()) {
if ($block = $this->getLayout()->getBlock($blockName)) {
return $block;
}
}
$block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, microtime());
return $block;
}
This, more than anything, is a nice example of the big challenge Magento faces w/r/t developer uptake. After 5+ years of development, Magento 2 still presents the same set of challenges as Magento 1. It also presents a whole new slew of challenges introduced by Magento 2. It also shows no signs, as an organization (hello hard working individuals fighting the good fight!), of realizing this and making the necessary adjustments.
All of this won’t prevent Magento from being a financial success – but it’s created a new platform where even its die-hard advocates are taking hard looks at other options.