Criar um atributo de produto no Magento 2 com data patch
Atributos de produto podem ser criados no admin em um minuto, então por que escrever código para eles? Porque um atributo criado à mão em produção não existe no staging, na máquina do desenvolvedor nem no banco do seu CI. Se o seu módulo lê esse atributo, ele precisa criá-lo. No Magento 2.3 em diante, o jeito de fazer isso é um data patch. Este guia mostra como escrever um para Magento Open Source e Adobe Commerce 2.4.8, com as opções que importam e os erros que mais aparecem.
Como os data patches funcionam
Um data patch é uma classe em Setup/Patch/Data que implementa Magento\Framework\Setup\Patch\DataPatchInterface. Durante o bin/magento setup:upgrade, o Magento executa todo patch cujo nome de classe ainda não está na tabela patch_list e depois o registra ali. Um patch roda uma vez por banco de dados. Não há número de versão para incrementar, como nos antigos scripts InstallData e UpgradeData.
Um patch declara outros patches de que depende com getDependencies() e nomes antigos que ele substitui com getAliases(). Para o quadro geral de schema e setup de dados, veja o artigo sobre declarative schema e data patches.
Um atributo de texto
<?php
declare(strict_types=1);
namespace Acme\Catalog\Setup\Patch\Data;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
class AddManufacturerPartNumberAttribute implements DataPatchInterface, PatchRevertableInterface
{
private const ATTRIBUTE_CODE = 'manufacturer_part_number';
public function __construct(
private readonly ModuleDataSetupInterface $moduleDataSetup,
private readonly EavSetupFactory $eavSetupFactory
) {
}
public function apply(): self
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
$eavSetup->addAttribute(Product::ENTITY, self::ATTRIBUTE_CODE, [
'type' => 'varchar',
'label' => 'Manufacturer Part Number',
'input' => 'text',
'required' => false,
'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'group' => 'General',
'sort_order' => 50,
'visible' => true,
'user_defined' => true,
'searchable' => true,
'filterable' => false,
'comparable' => false,
'visible_on_front' => true,
'used_in_product_listing' => false,
'is_used_in_grid' => true,
'is_visible_in_grid' => false,
'is_filterable_in_grid' => true,
'unique' => false,
]);
return $this;
}
public function revert(): void
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
$eavSetup->removeAttribute(Product::ENTITY, self::ATTRIBUTE_CODE);
}
public static function getDependencies(): array
{
return [];
}
public function getAliases(): array
{
return [];
}
}
Rode bin/magento setup:upgrade e o atributo aparece no grupo General de todos os attribute sets de produto.
Um detalhe a saber antes do primeiro deploy: se já existe um atributo com o mesmo code, o addAttribute() o atualiza com as configurações do seu patch em vez de falhar. Isso é conveniente quando um atributo foi criado primeiro à mão em produção e agora você quer ele em código. Também significa que o seu patch vai sobrescrever o que um admin tiver alterado. Compare as configurações de produção com o patch antes de publicar.
As opções que importam
O array passado ao addAttribute() usa chaves curtas que o catalog setup mapeia para colunas de eav_attribute e catalog_eav_attribute. As que vale pensar a cada vez:
- type define a tabela de backend:
varchar,int,decimal,text,datetimeoustatic. Atributos select guardam IDs de opções, então usamint. Multiselect guarda uma lista separada por vírgulas e usavarcharoutext. - input é o campo do admin:
text,textarea,select,multiselect,boolean,date,pricee mais alguns. - global é o scope.
SCOPE_GLOBAL,SCOPE_WEBSITEouSCOPE_STORE. Use global a menos que o valor realmente mude por store view. Atributos com scope de store multiplicam as linhas nas tabelas de valores e deixam a indexação mais lenta. - group coloca o atributo nesse grupo de todos os attribute sets. Deixe de fora se quiser adicionar o atributo só a sets específicos.
- user_defined controla se os admins podem excluir o atributo. Defina como true para atributos customizados normais.
- searchable, filterable e filterable_in_search afetam o índice de busca e a navegação em camadas. Só ative quando precisar, porque cada um adiciona dados ao OpenSearch e trabalho à reindexação.
- used_in_product_listing adiciona o atributo aos dados carregados nas páginas de categoria. É fácil ligar e esquecer, e cada atributo habilitado deixa as queries de listagem mais pesadas.
- apply_to limita o atributo a tipos de produto, como uma string separada por vírgulas, por exemplo
simple,virtual. Vazio significa todos os tipos.
Mais sobre o lado de performance dessas flags em o artigo de boas práticas para atributos de produto.
Um atributo select com opções
Para um dropdown com uma lista fixa que os admins devem poder editar depois, deixe o Magento guardar as opções em eav_attribute_option:
$eavSetup->addAttribute(Product::ENTITY, 'material', [
'type' => 'int',
'label' => 'Material',
'input' => 'select',
'source' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class,
'required' => false,
'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'group' => 'General',
'user_defined' => true,
'filterable' => 1,
'searchable' => true,
'visible_on_front' => true,
'option' => [
'values' => ['Cotton', 'Linen', 'Wool', 'Polyester'],
],
]);
A chave option cria labels no nível do admin para cada valor. Labels por store view podem ser adicionados depois no admin ou em outro patch. Como as opções ficam guardadas como dados, os admins podem adicionar valores novos sem deploy, que costuma ser o que o negócio quer.
Um atributo select com source model
Quando as opções vêm do código ou de outro sistema, escreva um source model e aponte o source para ele:
<?php
declare(strict_types=1);
namespace Acme\Catalog\Model\Attribute\Source;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
class WarrantyPeriod extends AbstractSource
{
public function getAllOptions(): array
{
if ($this->_options === null) {
$this->_options = [
['value' => '', 'label' => __('-- Please Select --')],
['value' => 12, 'label' => __('12 months')],
['value' => 24, 'label' => __('24 months')],
['value' => 36, 'label' => __('36 months')],
];
}
return $this->_options;
}
}
Opções definidas assim não podem ser editadas no admin, e mudar um valor depois significa migrar os dados guardados. Use para listas que fazem parte da lógica de negócio, não para valores de marketing como cores ou materiais.
Se o módulo dono do source model for removido um dia, o atributo continua apontando para uma classe que não existe mais, e a página de edição do produto quebra. Remova o atributo ou troque o source dele antes de desinstalar, como explicado em o artigo sobre remover um módulo com segurança.
Adicionando a attribute sets específicos
Com user_defined true e sem group, o atributo é criado mas não é atribuído a nenhum attribute set. Para adicioná-lo a um set:
$entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);
$attributeSetId = $eavSetup->getAttributeSetId($entityTypeId, 'Clothing');
$groupId = $eavSetup->getAttributeGroupId($entityTypeId, $attributeSetId, 'General');
$eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'material', 60);
Os nomes dos attribute sets variam entre projetos. Se o set pode não existir, verifique antes e pule, para o patch não falhar em uma instalação nova.
Atualizando um atributo existente
Nunca edite um patch que já rodou. Ele não vai rodar de novo nos bancos que já o registraram, então os ambientes se afastam. Escreva um patch novo e use o updateAttribute():
$eavSetup->updateAttribute(Product::ENTITY, 'material', 'is_filterable_in_search', 1);
$eavSetup->updateAttribute(Product::ENTITY, 'material', 'used_in_product_listing', 0);
Declare o patch original em getDependencies() para a ordem ser garantida.
Erros comuns
- Esquecer o
sourceem um atributo select. O campo aparece sem opções, ou o frontend mostra IDs de opção em vez de labels. - Usar
varcharpara valores que serão filtrados ou ordenados como números. Escolha o tipo de backend pela forma como o dado é consultado. - Scope de store para tudo por padrão. Na prática, a maioria dos atributos é global.
- Apagar uma linha de
patch_listpara rodar um patch de novo em produção. Isso funciona em um banco local para testes, mas em ambientes compartilhados esconde o problema real, que é que a mudança pertence a um patch novo. - Rodar um reindex e limpar o cache depois não é opcional se o atributo é searchable ou filterable. Rode
bin/magento indexer:reindex catalogsearch_fulltexte os indexers de EAV, ou deixe o cron cuidar disso se os indexers estiverem em schedule.
FAQ
Como rodo um data patch de novo?
Em um banco local você pode apagar a linha dele em patch_list e rodar setup:upgrade. Para mudanças de verdade, escreva um patch novo, porque o original não vai rodar de novo nos ambientes que já o aplicaram.
Qual a diferença entre scope global, website e store?
Global guarda um valor para todas as stores. Os scopes de website e store permitem valores diferentes por website ou store view, ao custo de mais linhas e indexação mais lenta. Use o menor scope de que você realmente precisa.
O revert roda automaticamente quando eu desabilito o módulo?
Não. O revert roda quando o módulo é desinstalado com bin/magento module:uninstall, que só funciona para módulos instalados pelo Composer. Desabilitar um módulo não remove os atributos dele.
Por que meu atributo novo não aparece no frontend?
Confira visible_on_front, used_in_product_listing para páginas de categoria, a atribuição ao attribute set e se o template do tema realmente o renderiza. Depois reindexe e limpe o cache.
Atendo como freelancer em Magento e Adobe Commerce. Se você precisa de ajuda com a estrutura do catálogo ou com atributos EAV, fale comigo.
Artigos relacionados
Atributos de produto no Magento 2: boas práticas para um catálogo limpo
Como projetar atributos de produto no Magento 2: input types, scope, flags de busca e filtro, attribute sets, data patches e limpeza de atributos sem uso.
Declarative schema e data patches no Magento 2, com segurança
Altere o banco do Magento 2 sem quebrar a produção: db_schema.xml, whitelist, data e schema patches, patches revertable e o modo dry-run.
Order status customizado no Magento 2: states, statuses e workflow
Crie order statuses customizados no Magento 2, ligue-os a states com um data patch, altere-os pelo código com segurança e evite que o Magento os redefina ao salvar.
Magento 2: tipos de cache, clean, flush e cache type próprio
O que cada cache type do Magento 2 guarda, a diferença real entre cache:clean e cache:flush, caches invalidados e como criar seu próprio cache type.