Como criar/atualizar tabelas e dados com DataPatch e SchemaPatch no Magento 2
Tags: php, magento2, tutorial, braziliandevs
Published: 2021-10-15 11:37:41

# Contextualizando

A partir da versão 2.3 do Magento foi introduzido *Schema Patch* (pacote de esquemas) e *Data Patch* (pacote de dados), uma nova forma para os módulos aplicar mudança nos esquemas e dados das tabelas.

Diferente das configurações declarativas (arquivo `db_schema.xml`), a classe do tipo *Schema Patch* ou *Data Patch* são executadas apenas uma vez. Uma lista dos pacotes já executadas são armazenadas na tabela `patch_list` no banco de dados.

### Quando usar os pacotes de dados e esquemas?

Os pacotes podem ser executados apenas uma vez, e **não** dependem do atributo `setup_version` do arquivo `module.xml`. 

A classe *Schema Patch* entrou pra substituir as classes de *Install Schema* e *Upgrade Schema* na alteração dos esquemas das tabelas de módulos que não devem ser alterados diretamente (como módulos do Magento, de terceiros, entre outros). Com esta classe também é possível criar tabelas, mas para a criação de tabelas recomendo que utilize as configurações declarativas (arquivo `db_schema.xml`). Saiba como criar uma tabela personalizada no post "[Como criar tabelas personalizadas no banco de dados com db_schema.xml no Magento 2]([https://dev.to/santanaluc94/como-criar-tabelas-personalizadas-no-banco-de-dados-com-dbschema-xml-no-magento-2-3jlh](https://dev.to/santanaluc94/como-criar-tabelas-personalizadas-no-banco-de-dados-com-dbschema-xml-no-magento-2-3jlh))".

A classe *Data Patch* entrou pra substituir as classes de *Install Data* e *Upgrade Data* na inserção, remoção e alteração de dados nas tabelas.

---

# Código para a criação de classes SchemaPatch e DataPatch

### Schema Patch

A classe *Schema Patch* deve seguir a estrutura de pastas `\{Vendor}\{Module}\Setup\Patch\Schema\{SchemaPatchName}` e implementar a interface `\Magento\Framework\Setup\Patch\SchemaPatchInterface`, que obriga a classe a implementar os métodos `getAliases()`, `apply()`, `getDependencies()` e `getVersion()`.

Caso o módulo seja desinstalado, é recomendado a reversão do seu *Schema Patch*, que é possível ser feita através da interface `\Magento\Framework\Setup\Patch\PatchRevertableInterface` e implementando o método `revert()`.

```php
<?php

namespace {Vendor}\{Module}\Setup\Patch\Schema;

use Magento\Framework\Setup\Patch\SchemaPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use {Vendor}\{Module}\Setup\Patch\Schema\{DependencyName};
use Magento\Framework\DB\Ddl\Table;

class {SchemaPatchName} implements SchemaPatchInterface, PatchRevertableInterface
{
    public const TABLE_NAME = '{table_name}';

    public function __construct(
        private ModuleDataSetupInterface $moduleDataSetup
    ) {
    }

    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        
        // Code here that you want apply in the patch

        // Add new column to table
        if ($this->moduleDataSetup->getConnection()->isTableExists(self::TABLE_NAME)) {
            // Add new column to table
            $this->moduleDataSetup->getConnection()->addColumn(
                $this->moduleDataSetup->getTable(self::TABLE_NAME),
                '{new_column}',
                [
                    'type' => Table::TYPE_{NAME},
                    'nullable' => {true/false},
                    'length' => '{length}',
                    'comment'  => '{column comment}',
                    'after' => '{column_name}'
                ]
            );
        
            // Add new foreign key to table
            $this->moduleDataSetup->getConnection()->addForeignKey(
                '{CURRENT_TABLE_NAME}_{COLUMN_NAME}_{REFERENCE_TABLE_NAME}_{REFERENCE_COLUMN_NAME}',
                self::TABLE_NAME,
                '{column_name}',
                '{reference_table_name}',
                '{reference_column_name}',
                '{TRIGGER FOREIGN VALUE}',
            );

            // Add new index column to table
            $this->moduleDataSetup->getConnection()->addIndex(
                self::TABLE_NAME,
                '{CURRENT_TABLE_NAME}_{COLUMN_NAME}',
                '{column_name}',
                '{index_value}'
            );

            // Change column to table
            $this->moduleDataSetup->getConnection()->changeColumn(
                $this->moduleDataSetup->getTable(self::TABLE_NAME),
                '{old_column_name}',
                '{new_column_name}',
                [
                    'type' => Table::TYPE_{NAME},
                    'length' => '{length}',
                    'nullable' => {true/false},
                    'comment' => '{column comment}'
                ]
            );

            // Remove column from table
            $this->moduleDataSetup->getConnection()->dropColumn(
                $this->moduleDataSetup->getTable('{table_name}'),
                '{column_name}'
            );

            // Remove table
            $this->moduleDataSetup->getConnection()->dropTable(
                $this->moduleDataSetup->getTable('{table_name}')
            );
        }

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public static function getDependencies(): array
    {
        return [
            {DependencyName}::class,
            {DependencyName}::class,
            {DependencyName}::class
        ];
    }

    public function getAliases(): array
    {
        return [];
    }

    public function getVersion(): string
    {
        return '{version_number}';
    }

    public function revert(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        // Code here that you want revert in the patch
        $this->moduleDataSetup->getConnection()->endSetup();
    }
}
```

- `getAliases()`: método onde é definido o alias para o *Schema Patch*, caso um *Schema Patch* sofra uma alteração no nome e não seja necessário a execução do mesmo novamente, seu alias pode ser retornado nessa função dentro de um *array*
- `apply()`: método que deve ter a execução principal, onde os esquemas serão modificados, adicionados ou removidos
- `getDependencies()`: método que pode conter as dependências em forma de *array* com classes do Magento que serão executados antes do *Schema Patch* atual. Entretanto se o *array* retornado for vazio significa que o *Schema Patch* é independente de qualquer atualização
- `getVersion()`: método que retornar em formato de *string* o número da versão. Se a versão do módulo no banco de dados é maior que a versão especificado no *Data Patch*, o método `apply()` não será executado. Caso a versão for menor ou igual, será executado

### Data Patch

A classe *Data Patch* deve seguir a estrutura de pastas `\{Vendor}\{Module}\Setup\Patch\Data\{DataPatchName}` e implementar a interface `\Magento\Framework\Setup\Patch\DataPatchInterface`, que obriga a classe a implementar os métodos `getAliases()`, `apply()`, `getDependencies()` e `getVersion()`.

Caso o módulo seja desinstalado, é recomendado a reversão do seu *Data Patch*, que é possível ser feita através da interface `\Magento\Framework\Setup\Patch\PatchRevertableInterface` e implementando o método `revert()`.

```php
<?php

namespace {Vendor}\{Module}\Setup\Patch\Data;

use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use {Vendor}\{Module}\Setup\Patch\Data\{DependencyName};

class {DataPatchName} implements DataPatchInterface, PatchRevertableInterface
{
    public const TABLE_NAME = '{table_name}';

    public function __construct(
        private ModuleDataSetupInterface $moduleDataSetup
    ) {
    }

    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        // Code here that you want apply in the patch

        if ($this->moduleDataSetup->getConnection()->isTableExists(self::TABLE_NAME)) {
            $data = [
                '{column_name_1}' => '{value}',
                '{column_name_2}' => 1,
                '{column_name_3}' => {true/false},
                '{column_name_n}' => '0000-00-00'
            ];

            $this->moduleDataSetup->getConnection()->insert(
                $this->moduleDataSetup->getTable(self::TABLE_NAME),
                $data
            );
        }

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public static function getDependencies(): array
    {
        return [
            {DependencyName}::class
        ];
    }

    public function getAliases(): array
    {
        return [];
    }

    public function getVersion(): string
    {
        return '{version_number}';
    }

    public function revert(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        // Code here that you want revert in the patch

        if ($this->moduleDataSetup->getConnection()->isTableExists(self::TABLE_NAME)) {
            $this->moduleDataSetup->getConnection()->delete(
                $this->moduleDataSetup->getTable(self::TABLE_NAME),
                ['{column_name} = ?' => '{value}']
            );
        }

        $this->moduleDataSetup->getConnection()->endSetup();
    }
}
```

- `getAliases()`: método onde é definido o alias para o *Data Patch*, caso um *Data Patch* sofra uma alteração no nome e não seja necessário a execução do mesmo novamente, seu alias pode ser retornado nessa função dentro de um *array*
- `apply()`: método que deve ter a execução principal, onde os dados serão modificados, adicionados ou removidos
- `getDependencies()`: método que pode conter as dependências em forma de *array* com classes do Magento que serão executados antes do Data Patch atual. Entretanto se o *array* retornado for vazio significa que o *Data Patch* é independente de qualquer atualização
- `getVersion()`: método que retornar em formato de *string* o número da versão. Se a versão do módulo no banco de dados é maior que a versão especificado no *Data Patch*, o método `apply()` não será executado. Caso a versão for menor ou igual, será executado

---

# Finalizando

> Valores entre chaves (`{test}`) devem ser alterados na implementação do código.

## Habilitando as alterações

Comando para atualizar os dados do banco de dados e o esquema do banco de dados.

```bash
php bin/magento setup:upgrade
```

## Diretórios e Arquivos

Segue a a lista de diretórios e arquivos que devem ser criados.

```
- app/
  - code/
    - {Vendor}/
        - {Module}/
          - etc/
						- module.xml
          - Setup/
            - Patch/
              - {Schema/Data}/
                - {ClassName}.php
          - registration.php
          - composer.json
```