← Voltar ao blog

Layout XML do Magento 2: guia prático para desenvolvedores

Rodrigo Biazzi

O layout XML é a parte do Magento 2 que decide quais blocks renderizam numa página, em qual ordem, dentro de quais containers e com quais templates. Quase toda tarefa de frontend passa por ele, de adicionar um block na página de produto a remover algo que um módulo de terceiros injetou no header. É também a parte onde mais vejo copiar e colar sem entender, o que gera páginas que quebram no próximo upgrade.

Este guia cobre como o layout XML funciona no Magento 2.4.8, as instruções que você vai usar toda semana e os hábitos que mantêm as mudanças de layout pequenas e seguras.

Onde ficam os arquivos de layout

Um módulo traz seus arquivos de layout em view/frontend/layout/ ou view/adminhtml/layout/. Um tema os traz dentro da pasta do módulo que ele visa, por exemplo app/design/frontend/Vendor/theme/Magento_Catalog/layout/. O nome do arquivo é o layout handle. Quando o Magento monta uma página, ele coleta todo arquivo com um handle correspondente de todos os módulos ativos e da cadeia de temas, e os mescla numa árvore só.

Alguns handles que você vai usar muito:

  • default carrega em toda página.
  • catalog_product_view carrega em toda página de produto. O Magento também adiciona handles mais específicos como catalog_product_view_type_configurable e catalog_product_view_id_42.
  • catalog_category_view carrega em páginas de categoria, com catalog_category_view_type_layered para anchor categories.
  • cms_index_index é a home, cms_page_view é qualquer outra página CMS.
  • checkout_cart_index e checkout_index_index são o cart e o checkout.

O handle de qualquer controller segue o padrão routeid_controller_action. Se você não tem certeza de quais handles uma página carrega, registre em log o resultado de $this->getLayout()->getUpdate()->getHandles() a partir de um block durante o desenvolvimento.

Page layouts e page configuration

Existem três tipos de arquivo de layout, e misturá-los é uma fonte comum de confusão.

  • Page layouts ficam em page_layout/ e definem o esqueleto: empty, 1column, 2columns-left, 2columns-right e 3columns. Só contêm containers.
  • Os arquivos de page configuration ficam em layout/ e são os que você mais edita. Podem definir o page layout e mudar o head e o body.
  • Generic layouts são usados em respostas AJAX e em algumas partes do admin. Têm um elemento raiz layout em vez de page.

Um arquivo de page configuration típico é assim:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      layout="1column"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Vendor_Module::css/product-extra.css"/>
        <remove src="Some_Module::js/legacy-tracker.js"/>
    </head>
    <body>
        <referenceContainer name="product.info.main">
            <block class="Magento\Framework\View\Element\Template"
                   name="vendor.product.delivery"
                   template="Vendor_Module::product/delivery.phtml"
                   after="product.info.price"/>
        </referenceContainer>
    </body>
</page>

O atributo layout em page define o page layout daquele handle. O admin também pode definir um layout por página CMS, categoria ou produto, e essa escolha vence o XML para aquela entidade.

Containers e blocks

Um container é um elemento estrutural sem classe PHP e sem template. Ele renderiza seus filhos em ordem e pode envolvê-los numa tag HTML. Um block é um objeto PHP, geralmente com um template, que produz HTML.

<container name="vendor.usp.wrapper" htmlTag="div" htmlClass="usp-bar" label="USP Bar">
    <block class="Magento\Cms\Block\Block" name="vendor.usp.cms">
        <arguments>
            <argument name="block_id" xsi:type="string">usp-bar</argument>
        </arguments>
    </block>
</container>

Um container sem filhos e sem htmlTag não renderiza nada, o que é útil. Você pode adicionar containers vazios como pontos de extensão e eles não custam nada nas páginas onde ninguém os usa.

Para blocks novos, use Magento\Framework\View\Element\Template como classe e ponha a lógica num view model. Escrever uma classe de block customizada só para expor alguns métodos a um template é um padrão mais antigo que dificulta testes e reuso.

View models

Um view model é uma classe simples que implementa Magento\Framework\View\Element\Block\ArgumentInterface. Você o passa como argument e o busca no template.

<referenceBlock name="vendor.product.delivery">
    <arguments>
        <argument name="view_model" xsi:type="object">Vendor\Module\ViewModel\Delivery</argument>
    </arguments>
</referenceBlock>
<?php
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Vendor\Module\ViewModel\Delivery $viewModel */
$viewModel = $block->getData('view_model');
?>
<p class="delivery-estimate"><?= $escaper->escapeHtml($viewModel->getEstimateText()) ?></p>

O view model recebe suas dependências pelo construtor como qualquer outra classe, então pode ser testado em unit test sem renderizar nada.

As instruções que você usa toda semana

referenceBlock e referenceContainer

Eles apontam para um elemento declarado em outro lugar, para você adicionar filhos, mudar arguments ou mudar atributos. Se o nome referenciado não existe naquela página, o Magento ignora a instrução sem erro. É conveniente, mas também significa que um erro de digitação falha em silêncio, então confira o resultado no navegador.

remove e display

<referenceBlock name="product.info.mailto" remove="true"/>
<referenceBlock name="catalog.compare.sidebar" display="false"/>

O remove="true" tira o elemento e seus filhos da árvore. O display="false" mantém na árvore mas não renderiza, então outros arquivos de layout ainda podem referenciá-lo ou tirar os filhos dele de dentro. Quando outro módulo move algo para dentro de um block que você removeu, o elemento movido some junto. Se isso te pegar, troque por display="false".

move

<move element="product.info.stock.sku" destination="product.info.main" before="product.info.price"/>

O move muda o pai e a posição de um elemento. Use before e after com o nome de um elemento, ou - para colocá-lo primeiro ou último. Não remova um block num arquivo e o redeclare em outro só para mudar a posição. Isso quebra todo outro módulo que referencia o original.

update

<update handle="customer_account"/> traz todas as instruções de outro handle. As páginas da conta do cliente usam isso para compartilhar a navegação lateral. É uma forma limpa de compartilhar um layout entre os seus próprios controllers.

Arguments e ifconfig

Arguments suportam vários valores de xsi:type: string, boolean, number, array, object, url, helper e options. Strings podem ser traduzidas com translate="true".

Blocks podem ser exibidos de forma condicional com ifconfig, que lê um caminho de config booleano:

<block class="Magento\Framework\View\Element\Template"
       name="vendor.banner"
       template="Vendor_Module::banner.phtml"
       ifconfig="vendor_module/general/enabled"/>

Sobrescrever vs estender arquivos de layout

Um arquivo de tema com o mesmo nome de um arquivo de módulo o estende. Os dois são mesclados, e as instruções do tema são aplicadas depois. É o que você quer quase sempre.

O Magento também suporta overrides completos. Um arquivo em Vendor/theme/Magento_Catalog/layout/override/base/catalog_product_view.xml substitui o arquivo do módulo por inteiro, e override/theme/Magento/luma/ substitui um arquivo do tema pai. Eu evito. Você perde toda correção que o módulo trouxer naquele arquivo no próximo upgrade, e nada avisa.

Blocks cacheable e full page cache

Um block pode ser declarado com cacheable="false". Isso não deixa só aquele block sem cache. Deixa a página inteira não cacheável para o full page cache, em toda página onde aquele handle carrega. Um único block assim no default.xml desliga o full page cache da loja inteira. É uma das primeiras coisas que procuro numa auditoria de performance:

grep -rn 'cacheable="false"' app/code app/design vendor --include=*.xml | grep -v '/Test/'

Conteúdo específico do customer deve vir de customer data sections carregadas com JavaScript, para o HTML ser igual para todo visitante e o Varnish poder servi-lo.

Depurando o layout

  • Limpe o cache de layout e de página depois das mudanças: bin/magento cache:clean layout full_page.
  • Ative os template hints com bin/magento dev:template-hints:enable para ver qual template renderiza cada parte da página.
  • Valide o XML contra o schema na sua IDE. O PhpStorm resolve os caminhos urn:magento depois que você roda bin/magento dev:urn-catalog:generate .idea/misc.xml.
  • Quando um block não aparece, confira se o container pai existe naquele handle e se nenhum outro arquivo o remove.

Se você está montando um tema do zero, o guia de child theme mostra onde esses arquivos ficam numa pasta de tema real.

FAQ

Qual a diferença entre um block e um container no Magento 2?

Um container não tem classe nem template. Ele só agrupa filhos e pode envolvê-los numa tag HTML. Um block é um objeto PHP, geralmente renderizando um template, que produz o HTML de fato.

Por que minha mudança com referenceBlock não tem efeito?

O nome está errado, o block não faz parte daquele handle, ou outro arquivo de layout aplicado depois o remove ou muda. O Magento ignora referências a elementos inexistentes sem erro, então confira o nome e os handles que carregam naquela página.

Devo usar remove="true" ou display="false"?

Use display="false" quando outros módulos podem referenciar o block ou mover filhos para dentro dele. Use remove="true" quando você quer o elemento e todos os filhos fora e sabe que nada depende dele.

O cacheable="false" afeta só um block?

Não. Ele marca a página inteira como não cacheável para o full page cache. Carregue dados específicos do customer com customer data sections.

Atendo como freelancer em Magento e Adobe Commerce, incluindo correções de tema e frontend. Se você precisa de ajuda com isso, fale comigo.

Artigos relacionados