Como criar atributos de extensão no Magento 2

Lucas Teixeira dos Santos Santana - Mar 17 '22 - - Dev Community

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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];
    }
}
Enter fullscreen mode Exit fullscreen mode

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];
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .