Contextualizando
O que é um atributo de extensão?
Os atributos no Magento 2 são propriedades das entidades que auxiliam na variedade de opções. Os módulos personalizados não podem alterar as interfaces das entidades nativas, entretanto, a maioria das interfaces das entidades possuem um funcionalidade chamada atributos de extensão. Para saber se a interface da entidade possui atributos de extensão, basta observar se a mesma estende da interface \Magento\Framework\Api\ExtensibleDataInterface
e possui os métodos getExtensionAttributes()
e setExtensionAttributes()
.
Código para criar um atributo
Criando o atributo de extensão
extension_attributes.xml
Para criar os atributos de extensão é necessário criar um arquivo chamado extension_attributes.xml
que deve seguir a estrutura de pasta \{Vendor}\{Module}\etc\extension_attributes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="{Vendor}\{Module}\Api\Data\{EntityName}Interface">
<attribute code="{attr_name_object}" type="{Vendor}\{Module}\{Directory}\{Class}" />
<attribute code="{attr_name_string}" type="string" />
<attribute code="{attr_name_boolean}" type="boolean" />
<attribute code="{attr_name_integer}" type="int" />
<attribute code="{attr_name_float}" type="float" />
<attribute code="{attr_name_array}" type="{type}[]" />
<attribute code="{attr_name_array}" type="{Vendor}\{Module}\Api\Data\{EntityName}Interface[]">
<resources>
<resource ref="{Vendor}_{Module}::{acl_path}" />
</resources>
<join reference_table="{table_name}" reference_field="{column_name}" join_on_field="{interface_field}">
<field>{field_name}</field>
</join>
</attribute>
</extension_attributes>
</config>
O nó <extension_attributes>
só possui um atributo, o for
, que indicará qual a entidade que receberá os novos atributos de extensão e que será implementada a interface \Magento\Framework\Api\ExtensibleDataInterface
e possuir os métodos getExtensionAttributes()
e setExtensionAttributes()
.
O nó <attribute>
é filho do nó <extension_attributes>
e é obrigatório, este possui o atributo code
, que será o nome do atributo (deverá ser escrito em snake case) e o atributo type
, que indicará o tipo do atributo.
O nó <resources>
é filho do nó <attribute>
é opcional e não possui atributos, apenas pode ter o nó filho <resource>
, já este possui o atributo ref
, que referencia a seção a um recurso ACL para fornecer permissões das configurações.
O nó <join>
é filho do nó <attribute>
é opcional e possui o atributo reference_table
, que faz uma operação de join com a tabela referenciada neste atributo, com o valor da coluna referenciada no atributo reference_field
e join_on_field
é a coluna da tabela associada a interface.
O nó <field>
é filho do nó <join>
e é obrigatório quando este for inserido. Este nó não possui atributos e nem outros nós filhos, apenas valor, que especifica a propriedade da tabela referenciada que irá unir a interface definida.
db_schema.xml
É necessário adicionar uma coluna na tabela desejada para o campo do atributo de extensão criado no arquivo extension_attributes.xml
.
Utilizarei o arquivo de db_schema.xml
para adicionar a coluna na tabela, mas pode ser feito como UpgradeSchema ou SchemaPatch.
<?xml version="1.0" ?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
...
<table name="{table_name)">
<column xsi:type="{column_type}" name="{column_name)" comment="{Column Comment}"/>
</table>
</schema>
Para saber mais sobre como criar e/ou atualizar campos na tabela via db_schema.xml
basta acessar o post "Como criar tabelas personalizadas no banco de dados com db_schema.xml no Magento 2".
Para saber mais sobre como criar e/ou atualizar campos na tabela via SchemaPatch
basta acessar o post "Como criar/atualizar tabelas e dados com DataPatch e SchemaPatch no Magento 2".
Para saber mais sobre como criar e/ou atualizar campos na tabela na forma antiga que o Magento utiliza, UpgradeSchema
, basta acessar o post "Como criar/atualizar tabelas com InstallSchema e UpgradeShema no Magento 2".
Acessando os atributos de extensão
Assim que criar o arquivo extension_attributes.xml
já fica disponível para acessar e definir valores dos atributos via métodos mágicos, apenas adicionando o prefixo get
e set
nos nomes definidos dos atributos.
$extAttributes = $entityName->getExtensionAttributes();
$extAttributes->getAttrNameObject();
$extAttributes->getAttrNameString();
$extAttributes->getAttrNameBoolean();
$extAttributes->getAttrNameInteger();
$extAttributes->getAttrNameFloat();
$extAttributes->getAttrNameArray();
$extAttributes->setAttrNameObject($objectValue);
$extAttributes->setAttrNameString($stringValue);
$extAttributes->setAttrNameBoolean($booleanValue);
$extAttributes->setAttrNameInteger($integerValue);
$extAttributes->setAttrNameFloat($floatValue);
$extAttributes->setAttrNameArray($arrayValue);
Atributo de extensão na entidade
Interface
No Magento, primeiro criamos a interface do repositório no caminho \{Vendor}\{Module}\Api\Data
. Caso a interface que queira adicionar os atributos de extensão não pertença ao código fonte do Magento 2, basta herdar a interface \Magento\Framework\Api\ExtensibleDataInterface
.
Para saber mais sobre como sobreescrever arquivos através no post "Como sobrescrever arquivos no Magento 2 através de preferences".
<?php
namespace {Vendor}\{Module}\Api\Data;
use Magento\Framework\Api\ExtensibleDataInterface;
interface {EntityName}Interface extends ExtensibleDataInterface
{
public const {ATTR_NAME} = '{attribute_name}';
public function get{MethodName}(): {type};
public function set{MethodName}(): self;
public function getExtensionAttributes(): \{Vendor}\{Module}\Api\Data\{EntityName}ExtensionInterface;
public function setExtensionAttributes(\{Vendor}\{Module}\Api\Data\{EntityName}ExtensionInterface $extensionAttributes): self;
}
di.xml
Após a criação da interface do repositório é necessário a sobrescrição através do arquivo di.xml
, para que o arquivo fique devidamente implementado e possa descrever em como os métodos serão executados.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
...
<preference for="{Vendor}\{Module}\Api\Data\{EntityName}Interface" type="{Vendor}\{Module}\Model\Data\{EntityName}"/>
...
</config>
Model
Criado a interface do repositório e a sobrescrição da interface através do arquivo di.xml
, falta apenas implementar os métodos da interface na classe.
<?php
namespace {Vendor}\{Module}\Model\Data;
use Magento\Framework\Model\AbstractExtensibleModel;
use {Vendor}\{Module}\Api\Data\{EntityName}Interface;
class {EntityName} extends AbstractExtensibleModel implements {EntityName}Interface
{
public function get{MethodName}(): {type}
{
return $this->_get({EntityName}Interface::{ATTR_NAME});
}
public function set{MethodName}(string $attrName): {EntityName}Interface
{
return $this->setData({EntityName}Interface::ATTR_NAME, $attrName);
}
public function getExtensionAttributes(): \{Vendor}\{Module}\Api\Data\{EntityName}ExtensionInterface
{
return $this->_getExtensionAttributes();
}
public function setExtensionAttributes(\{Vendor}\{Module}\Api\Data\{EntityName}ExtensionInterface $extensionAttributes) {
return $this->_setExtensionAttributes($extensionAttributes);
}
}
Plugin para salvar/recuperar os dados do atributo de extensão
di.xml
Para saber mais sobre plugin basta acessar o post "Como criar plugins no Magento 2".
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
...
<type name="Magento\{Module}\Model\{Directory}\{EntityName}">
<plugin name="{plugin_name}" type="{Vendor}\{Module}\Plugin\Model\{PluginEntityName}" disabled="false" sortOrder="10" />
</type>
<type name="Magento\{Module}\Model\{Directory}\{EntityName}Repository">
<plugin name="{plugin_name}" type="{Vendor}\{Module}\Plugin\{Directory}\{PluginRepositoryName}" disabled="false" sortOrder="20" />
</type>
...
</config>
Plugins
Será necessário criar o método afterLoad()
para adicionar os atributos de extensão no retorno do objeto quando forem recuperado do banco via a classe da entidade.
Será necessário criar o método beforeSave()
para adicionar valores no atributo de extensão antes de salvar o objeto no banco de dados através da entidade.
<?php
namespace {Vendor}\{Module}\Plugin\{Directory};
use Magento\{Module}\Api\Data\{EntityName}ExtensionFactory;
use Magento\{Module}\Api\Data\{EntityName}Interface as Magento{EntityName}Interface;
use {Vendor}\{Module}\Api\Data\{EntityName}Interface;
class {PluginEntityName}
{
public function __construct(
private {EntityName}ExtensionFactory $extensionAttributeFactory
) {
}
public function afterLoad(Magento{EntityName}Interface $subject): Magento{EntityName}Interface {
$extensionAttributes = $subject->getExtensionAttributes() ?: $this->extensionAttributeFactory->create();
$extensionAttributes->set{MethodName}($subject->getData({EntityName}Interface::ATTR_NAME));
$subject->setExtensionAttributes($extensionAttributes);
return $subject;
}
public function beforeSave(Magento{EntityName}Interface $subject): array
{
$extensionAttributes = $subject->getExtensionAttributes() ?: $this->extensionAttributeFactory->create();
if ($extensionAttributes !== null && $extensionAttributes->set{MethodName}() !== null) {
$subject->set{MethodName}($extensionAttributes->get{MethodName}());
}
return [$subject];
}
}
Será necessário criar os métodos afterGetById()
e afterGetList()
para adicionar os atributos de extensão no retorno do(s) objeto(s) quando forem recuperado do banco via o repositório.
Será necessário criar o método beforeSave()
para adicionar valores no atributo de extensão antes de salvar o objeto no banco de dados através do repositório.
<?php
namespace {Vendor}\{Module}\Plugin\{Directory};
use Magento\{Module}\Api\Data\{EntityName}ExtensionFactory;
use Magento\{Module}\Api\{EntityName}RepositoryInterface;
use {Vendor}\{Module}\Api\Data\{EntityName}Interface;
use Magento\{Module}\Api\Data\{EntityName}Interface as Magento{EntityName}Interface;
use Magento\{Module}\Api\Data\{EntityName}SearchResultsInterface;
class {PluginRepositoryName}
{
public function __construct(
private {EntityName}ExtensionFactory $extensionAttributeFactory
) {
}
public function afterGetById(
{EntityName}RepositoryInterface $subject,
Magento{EntityName}Interface $entity
): Magento{EntityName}Interface {
$extensionAttributes = $entity->getExtensionAttributes() ?: $this->extensionAttributeFactory->create();
$extensionAttributes->set{MethodName}($entity->getData({EntityName}Interface::ATTR_NAME));
$entity->setExtensionAttributes($extensionAttributes);
return $entity;
}
public function afterGetList(
{EntityName}RepositoryInterface $subject,
{EntityName}SearchResultsInterface $resultCart
) {
/** @var Magento{EntityName}Interface $entity */
foreach ($resultCart->getItems() as $entity) {
$this->afterGetById($subject, $entity);
}
return $resultCart;
}
public function beforeSave(
{EntityName}RepositoryInterface $subject,
Magento{EntityName}Interface $entity
): array {
$extensionAttributes = $entity->getExtensionAttributes() ?: $this->extensionAttributeFactory->create();
if ($extensionAttributes !== null && $extensionAttributes->set{MethodName}() !== null) {
$entity->set{MethodName}($extensionAttributes->get{MethodName}());
}
return [$entity];
}
}
Finalizando
Valores entre chaves (
{test}
) devem ser alterados na implementação do código.
Habilitando as alterações
Apague os arquivos que são gerados na compilação do Magento e execute o comando PHP para gerar a configuração das injeções de dependência e todas as classes ausentes que precisam ser geradas (proxys, interceptors, etc) e para limpar todos os caches de armazenamento em cache do processos.
rm -rf var/generation/
rm -rf generated/
php bin/magento setup:di:compile
php bin/magento cache:clean
php bin/magento flush
Diretórios e Arquivos
Segue a a lista de diretórios e arquivos que devem ser criados.
- app/
- code/
- {Vendor}/
- {Module}/
- etc/
- module.xml
- extension_attributes.xml
- registration.php
- composer.json