Aplicando patches no core do Magento 2 com Composer Patches
Mais cedo ou mais tarde todo projeto Magento precisa mudar código que não é dele. Um bug em um módulo do core é corrigido em uma release posterior para a qual você ainda não consegue atualizar. Uma extensão de terceiros tem um erro de digitação em uma query SQL. Um método privado não tem ponto de plugin. Editar o arquivo em vendor/ resolve só até alguém rodar composer install.
Patches são a resposta honesta para esse problema. A mudança fica em um arquivo no repositório, o Composer a aplica toda vez que instala o pacote e o build falha de forma barulhenta quando o patch deixa de aplicar. Este artigo cobre as duas ferramentas usadas para isso no Magento 2: cweagans/composer-patches para os seus próprios patches e o Quality Patches Tool para os patches publicados pela Adobe.
Quando um patch é a ferramenta certa
Um patch é último recurso, não o primeiro. Antes de escrever um, veja se o comportamento pode ser mudado pelos pontos de extensão normais:
- Um plugin em um método público
- Um observer em um evento disparado por perto
- Uma preference para uma classe, ou uma substituição de argumento no
di.xml - Um override de template ou JS no seu tema, ou um mixin do RequireJS
A comparação dessas opções está em plugins vs observers vs preferences. Um patch faz sentido quando o código está em um método private ou protected que uma preference obrigaria você a copiar inteiro, quando é uma pequena correção em um método longo, ou quando a correção já foi publicada upstream e você só precisa dela até o próximo upgrade.
Configurando o composer-patches
Instale o plugin como dependência do projeto:
composer require cweagans/composer-patches
O Composer vai perguntar se deve permitir o plugin. Permita, o que o adiciona em config.allow-plugins no composer.json. Os patches são declarados em extra.patches, com o nome do pacote como chave:
{
"extra": {
"patches": {
"magento/module-catalog": {
"Fix wrong store filter in product collection": "patches/composer/magento-module-catalog-store-filter.patch"
},
"vendor-name/module-shipping-rates": {
"Fix null check in rate calculation": "patches/composer/vendor-shipping-null-check.patch"
}
}
}
}
O Composer aplica cada patch logo depois de instalar ou atualizar o pacote correspondente. A descrição é texto livre, então escreva algo que um colega entenda sem abrir o arquivo. Um link para a issue do GitHub ou para o ticket de suporte ajuda muito quando alguém depois se pergunta se o patch ainda é necessário.
As versões 1 e 2 do plugin se comportam de forma diferente em alguns pontos. A versão 2 mantém um arquivo patches.lock.json e adiciona comandos como composer patches-relock e composer patches-repatch. A versão 1 usa a opção composer-exit-on-patch-failure em extra para que um patch com falha interrompa a instalação. Veja qual versão o seu projeto usa e leia a documentação dela, porque os padrões em caso de falha não são os mesmos.
Falhando de forma barulhenta
O pior resultado é um patch que deixa de aplicar em silêncio depois de um upgrade. O site faz deploy, o bug volta e ninguém liga uma coisa à outra. Qualquer que seja a versão do plugin, garanta que um patch que não aplica quebre o build. Com a versão 1 isso significa definir:
{
"extra": {
"composer-exit-on-patch-failure": true
}
}
Seu pipeline de CI deve rodar um composer install limpo a cada mudança. É onde um patch quebrado vai aparecer primeiro. Veja CI/CD com GitHub Actions para um pipeline que faz isso.
Criando um patch file limpo
O patch precisa usar caminhos relativos à raiz do pacote, não à raiz do projeto. Para o magento/module-catalog, um caminho como Model/Product.php está certo, e vendor/magento/module-catalog/Model/Product.php não.
Um jeito confiável de produzir isso é inicializar um repositório Git temporário dentro da pasta do pacote:
cd vendor/magento/module-catalog
git init -q && git add -A && git commit -qm "original"
# edit the file(s) you need to change
git diff > ../../../patches/composer/magento-module-catalog-store-filter.patch
rm -rf .git
cd -
O diff resultante tem caminhos a/Model/Product.php e b/Model/Product.php, que o plugin aplica com os níveis de patch habituais. Depois reinstale o pacote para confirmar que o patch aplica em uma cópia limpa:
rm -rf vendor/magento/module-catalog
composer install
Patches do repositório magento2 no GitHub
Um commit no GitHub do magento/magento2 usa caminhos de monorepo como app/code/Magento/Catalog/Model/Product.php. Esses caminhos não existem em uma instalação via Composer. Baixe o commit como diff, troque o prefixo app/code/Magento/Catalog/ para que os caminhos comecem na raiz do módulo e mantenha só os hunks de um pacote por patch file. Um patch que toca dois pacotes precisa ser dividido em duas entradas no composer.json.
Mantenha os patches pequenos
Um patch por correção. Um único patch que toca dez coisas é impossível de descartar parcialmente depois de um upgrade. Patches pequenos com descrições claras podem ser removidos um de cada vez quando a correção upstream chega.
O Quality Patches Tool
A Adobe publica correções de problemas conhecidos como patches individuais com IDs. O Quality Patches Tool, distribuído como magento/quality-patches e usado por meio do magento/magento-cloud-patches no Adobe Commerce Cloud, sabe quais patches existem e quais servem para a sua versão instalada.
composer require magento/quality-patches
vendor/bin/magento-patches status
vendor/bin/magento-patches apply ACSD-12345
O status lista os patches disponíveis para a sua versão e se estão aplicados. O apply recebe um ou mais IDs de patch. O ID acima é um placeholder, então use os listados pelo status ou citados em artigos de suporte da Adobe. A ferramenta também tem um comando revert.
No Adobe Commerce Cloud, os patches geralmente são declarados no .magento.env.yaml, na variável QUALITY_PATCHES do estágio de build, para serem aplicados em todo build. Em outras hospedagens, rode o comando apply como parte do seu script de build, depois do composer install e antes do setup:di:compile. Patches aplicados assim não estão no Git, então o passo de build precisa rodar em todo deploy.
Antes de aplicar qualquer coisa, leia a descrição do patch e confira se ele ataca o seu problema real. Alguns patches mudam comportamentos dos quais outras extensões dependem.
Onde guardar os patches no repositório
Uma estrutura simples que escala:
patches/composer/para os arquivos referenciados nocomposer.json- Um
READMEcurto nessa pasta listando cada patch, por que ele existe e a condição para removê-lo - Nomes de arquivo que começam com o nome do pacote, para que fiquem agrupados na ordenação
Alguns times preferem manter a lista de patches em um arquivo JSON separado referenciado por extra.patches-file. Isso funciona com a versão 1 do plugin e deixa o composer.json mais curto. De qualquer jeito, a lista precisa estar sob controle de versão.
Patches e upgrades
Todo upgrade do Magento começa pela lista de patches. Para cada entrada:
- Veja se a correção faz parte da nova release. As release notes e a issue do GitHub vinculada geralmente dizem.
- Se faz, remova a entrada e o arquivo.
- Se não faz, rode
composer installe veja se ainda aplica. - Se não aplica, recrie a partir do código novo usando os passos acima e depois teste o bug original de novo.
O checklist de upgrade para a 2.4.8 inclui essa revisão como um dos primeiros passos, porque um patch que falha bloqueia todo o update do Composer.
Aplicando patches em extensões de terceiros
O mesmo mecanismo funciona para extensões instaladas pelo Composer. Antes de aplicar um patch em uma, reporte o problema ao fornecedor. Se ele publicar uma correção, você pode descartar o patch no próximo update. Se a extensão está em app/code em vez de vendor, não há nada para o Composer corrigir e a mudança é só um commit normal, o que é mais um motivo para instalar extensões pelo Composer quando o fornecedor oferece.
O que não aplicar patch
Evite aplicar patch em código gerado em generated/, em arquivos estáticos compilados em pub/static ou no schema do banco por um patch file. São saídas de build ou estado de runtime. Corrija a fonte que os gera. Evite também patches que mudam assinaturas de métodos públicos. Outros módulos podem depender delas, e a próxima pessoa que ler o código não vai esperar que uma interface do core seja diferente da documentação.
FAQ
Posso simplesmente editar arquivos em vendor/magento?
Não. Qualquer composer install ou update pode sobrescrevê-los, e a mudança fica invisível no code review. Coloque a mudança em um patch file aplicado pelo Composer, ou mova para um módulo com um plugin ou preference.
Qual a diferença entre composer patches e o Quality Patches Tool?
O composer-patches aplica qualquer patch file que você adicione ao composer.json. O Quality Patches Tool aplica patches publicados pela Adobe para problemas conhecidos do Magento, identificados por um ID, e confere se servem para a sua versão instalada.
Meu patch falha depois de um upgrade. E agora?
Veja se a correção já está na nova release e descarte o patch se estiver. Se não, aplique a mudança antiga na mão no novo código do vendor e gere o patch de novo a partir de um diff limpo.
Patches são melhores do que plugins?
Use um plugin, observer ou preference quando os pontos de extensão permitirem. Um patch é para os casos em que o código que você precisa mudar é privado, está dentro de um método longo ou em um arquivo de template ou JS sem um override limpo.
Atendo como freelancer em Magento e Adobe Commerce, incluindo manutenção de upgrades e patches. Se precisar de ajuda com isso, fale comigo.
Artigos relacionados
Patches de segurança do Magento 2: como manter a loja em dia
Como funcionam os patches de segurança do Magento 2: patch releases, hotfixes isolados, Quality Patches Tool, aplicação com Composer e em quanto tempo agir.
Checklist de upgrade para o Magento 2.4.8, passo a passo
Checklist prático para atualizar Magento Open Source ou Adobe Commerce para a 2.4.8: requisitos de stack, extensões, passos do Composer, testes e go live.
Shipping method customizado no Magento 2: crie um módulo de carrier
Como criar um shipping method customizado no Magento 2.4: config.xml, system.xml, um carrier model com collectRates, regras de frete grátis e APIs externas de tarifa.
Checklist de limpeza de malware no Magento 2 para lojistas
Checklist defensivo para uma loja Magento 2 hackeada: sinais de comprometimento, achar código injetado em arquivos e banco, rebuild limpo, novas credenciais e hardening.