# Migrações de Dados (Migrations) ¶

Durante o curso de desenvolvimento e manutenção de uma aplicação orientada a banco de dados, a estrutura de banco sendo usada evolui ao mesmo tempo em que o código. Por exemplo, durante o desenvolvimento de uma aplicação, a criação de uma nova tabela pode ser necessária; após ser feito o deploy da aplicação em produção, pode ser descoberto que um índice deveria ser criado para melhorar a performance de alguma query; entre outros. Como a mudança de uma estrutura de banco de dados normalmente necessita de alguma mudança no código, o Yii suporta a então chamada funcionalidade de migração de dados que permite que você mantenha um registro das mudanças feitas no banco de dados em termos de migrações de dados que são versionadas em conjunto com o código fonte da aplicação.

Os seguintes passos mostram como uma migração de dados pode ser usada pela equipe durante o desenvolvimento:

1. João cria uma nova migração (ex. cria uma nova tabela, muda a definição de uma coluna, etc.).
2. João comita a nova migração no sistema de controle de versão (ex. Git, Mercurial).
3. Pedro atualiza seu repositório a partir do sistema de controle de versão e recebe a nova migração.
4. Pedro aplica a nova migração ao seu banco de dados local em seu ambiente de desenvolvimento, e assim, sincronizando seu banco de dados para refletir as mudanças que João fez.

E os seguintes passos mostram como fazer o deploy para produção de uma nova versão:

1. Luiz cria uma nova tag para o repositório do projeto que contem algumas novas migrações de dados.
2. Luiz atualiza o código fonte no servidor em produção para a tag criada.
3. Luiz aplica todas as migrações de dados acumuladas para o banco de dados em produção.

O Yii oferece um conjunto de ferramentas de linha de comando que permitem que você:

- crie novas migrações;
- aplique migrações;
- reverta migrações;
- reaplique migrações;
- exiba um histórico das migrações;

Todas estas ferramentas são acessíveis através do comando `yii migrate`. Nesta seção nós iremos descrever em detalhes como realizar várias tarefas usando estas ferramentas. Você também pode descobrir como usar cada ferramenta através do comando de ajuda `yii help migrate`.

> Observação: os migrations (migrações) podem afetar não só o esquema do banco de dados, mas também ajustar os dados existentes para se conformar ao novo esquema, como criar novas hierarquias de RBAC ou limpar dados de cache.

## Criando Migrações ¶

Para criar uma nova migração, execute o seguinte comando:

    yii migrate/create <nome>

O argumento obrigatório `nome` serve como uma breve descrição sobre a nova migração. Por exemplo, se a migração é sobre a criação de uma nova tabela chamada noticias, você pode usar o nome `criar_tabela_noticias` e executar o seguinte comando:

    yii migrate/create criar_tabela_noticias

> Observação: Como o argumento `nome` será usado como parte do nome da classe de migração gerada, este deve conter apenas letras, dígitos, e/ou underline.

O comando acima criará um novo arquivo contendo uma classe PHP chamada `m150101_185401_criar_tabela_noticias.php` na pasta `@app/migrations`. O arquivo contém o seguinte código que declara a classe de migração `m150101_185401_criar_tabela_noticias` com o código esqueleto:

    <?php

    use yii\db\Schema;
    use yii\db\Migration;

    class m150101_185401_criar_tabela_noticias extends Migration
    {
        public function up()
        {
        }

        public function down()
        {
            echo "m101129_185401_criar_tabela_noticias cannot be reverted.\n";
            return false;
        }
    }

Cada migração de dados é definida como uma classe PHP estendida de yii\db\Migration. O nome da classe de migração é automaticamente gerado no formato `m<YYMMDD_HHMMSS>_<Nome>`, onde

- `<YYMMDD_HHMMSS>` refere-se a data UTC em que o comando de criação da migração foi executado.
- `<Nome>` é igual ao valor do argumento `nome` que você passou no comando.

Na classe de migração, é esperado que você escreva no método `up()` as mudanças a serem feitas na estrutura do banco de dados. Você também pode escrever códigos no método `down()` para reverter as mudanças feitas por `up()`. O método `up()` é invocado quando você atualiza o seu banco de dados com esta migração, enquanto o método `down()` é invocado quando você reverte as mudanças no banco. O seguinte código mostra como você pode implementar a classe de migração para criar a tabela `noticias`:

    use yii\db\Schema;
    use yii\db\Migration;

    class m150101_185401_criar_tabela_noticias extends \yii\db\Migration
    {
        public function up()
        {
            $this->createTable('noticias', [
                'id' => Schema::TYPE_PK,
                'titulo' => Schema::TYPE_STRING . ' NOT NULL',
                'conteudo' => Schema::TYPE_TEXT,
            ]);
        }

        public function down()
        {
            $this->dropTable('noticias');
        }

    }

> Observação: Nem todas as migrações são reversíveis. Por exemplo, se o método `up()` deleta um registro de uma tabela, você possivelmente não será capaz de recuperar este registro com o método `down()`. Em alguns casos, você pode ter tido muita preguiça e não ter implementado o método `down()`, porque não é muito comum reverter migrações de dados. Neste caso, você deve retornar `false` no método `down()` para indicar que a migração não é reversível.

A classe base yii\db\Migration expõe a conexão ao banco através da propriedade db. Você pode usá-la para manipular o esquema do banco de dados usando os métodos como descritos em Trabalhando com um Esquema de Banco de Dados.

Ao invés de usar tipos físicos, ao criar uma tabela ou coluna, você deve usar tipos abstratos para que suas migrações sejam independentes do SGBD. A classe yii\db\Schema define uma gama de constantes para representar os tipos abstratos suportados. Estas constantes são nomeadas no formato `TYPE_<NOME>`. Por exemplo, `TYPE_PK` refere-se ao tipo chave primária auto incrementável; `TYPE_STRING` refere-se ao típo string. Quando a migração for aplicada a um banco de dados em particular, os tipos abstratos serão traduzidos nos respectivos tipos físicos. No caso do MySQL, `TYPE_PK` será traduzida para `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, enquanto `TYPE_STRING` será `varchar(255)`.

Você pode adicionar algumas constraints ao usar tipos abstratos. No exemplo acima, `NOT NULL` é adicionado a `Schema::TYPE_STRING` para especificar que a coluna não pode ser nula.

> Observação: O mapeamento entre tipos abstratos e tipos físicos é especificado pela propriedade $typeMap em cada classe `QueryBuilder`.

### Migrações Transacionais ¶

Ao realizar migrações de dados complexas, é importante assegurar que cada migração terá sucesso ou falhará por completo para que o banco não perca sua integridade e consistência. Para atingir este objetivo, recomenda-se que você encapsule suas operações de banco de dados de cada migração em uma transação.

Um jeito mais fácil de implementar uma migração transacional é colocar o seu código de migração nos métodos `safeUp()` e `safeDown()`. Estes métodos diferem de `up()` e `down()` porque eles estão implicitamente encapsulados em uma transação. Como resultado, se qualquer operação nestes métodos falhar, todas as operações anteriores sofrerão roll back automaticamente.

No exemplo a seguir, além de criar a tabela `noticias` nós também inserimos um registro inicial a esta tabela.

    use yii\db\Schema;
    use yii\db\Migration;

    class m150101_185401_criar_tabela_noticias extends Migration
    {
        public function safeUp()
        {
            $this->createTable('noticias', [
                'id' => 'pk',
                'titulo' => Schema::TYPE_STRING . ' NOT NULL',
                'conteudo' => Schema::TYPE_TEXT,
            ]);

            $this->insert('noticias', [
                'titulo' => 'título 1',
                'conteudo' => 'conteúdo 1',
            ]);
        }

        public function safeDown()
        {
            $this->delete('noticias', ['id' => 1]);
            $this->dropTable('noticias');
        }
    }

Observe que normalmente quando você realiza múltiplas operações em `safeUp()`, você deverá reverter a ordem de execução em `safeDown()`. No exemplo acima nós primeiramente criamos a tabela e depois inserimos uma túpla em `safeUp()`; enquanto em `safeDown()` nós primeiramente apagamos o registro e depois eliminamos a tabela.

> Observação: Nem todos os SGBDs suportam transações. E algumas requisições de banco não podem ser encapsuladas em uma transação. Para alguns exemplos, referir a commit implícito. Se este for o caso, implemente os métodos `up()` e `down()`.

### Métodos de Acesso ao Banco de Dados ¶

A classe base yii\db\Migration entrega vários métodos que facilitam o acesso e a manipulação de bancos de dados. Você deve achar que estes métodos são nomeados similarmente a métodos DAO encontrados na classe yii\db\Command. Por exemplo, o método yii\db\Migration::createTable() permite que você crie uma nova tabela assim como yii\db\Command::createTable() o faz.

O benefício ao usar os métodos encontrados em yii\db\Migration é que você não precisa criar explícitamente instancias de yii\db\Command e a execução de cada método automaticamente exibirá mensagens úteis que dirão a você quais operações estão sendo feitas e quanto tempo elas estão durando.

Abaixo está uma lista de todos estes métodos de acesso ao banco de dados:

- execute(): executando um SQL
- insert(): inserindo um novo registro
- batchInsert(): inserindo vários registros
- update(): atualizando registros
- delete(): apagando registros
- createTable(): criando uma tabela
- renameTable(): renomeando uma tabela
- dropTable(): removendo uma tabela
- truncateTable(): removendo todos os registros em uma tabela
- addColumn(): adicionando uma coluna
- renameColumn(): renomeando uma coluna
- dropColumn(): removendo uma coluna
- alterColumn(): alterando uma coluna
- addPrimaryKey(): adicionando uma chave primária
- dropPrimaryKey(): removendo uma chave primária
- addForeignKey(): adicionando uma chave estrangeira
- dropForeignKey(): removendo uma chave estrangeira
- createIndex(): criando um índice
- dropIndex(): removendo um índice

> Observação: yii\db\Migration não possui um método de consulta ao banco de dados. Isto porque você normalmente não precisará exibir informações extras ao recuperar informações de um banco de dados. E além disso você pode usar o poderoso Query Builder para construir e executar consultas complexas.

## Aplicando Migrações ¶

Para atualizar um banco de dados para a sua estrutura mais atual, você deve aplicar todas as migrações disponíveis usando o seguinte comando:

    yii migrate

Este comando listará todas as migrações que não foram alicadas até agora. Se você confirmar que deseja aplicar estas migrações, cada nova classe de migração executará os métodos `up()` ou `safeUp()` um após o outro, na ordem relacionada à data marcada em seus nomes. Se qualquer uma das migrações falhar, o comando terminará sem aplicar o resto das migrações.

Para cada migração aplicada com sucesso, o comando inserirá um registro numa tabela no banco de dados chamada `migration` para registrar uma aplicação de migração. Isto permitirá que a ferramenta de migração identifique quais migrações foram aplicadas e quais não foram.

> Observação: Esta ferramenta de migração automaticamente criará a tabela `migration` no banco de dados especificado pela opção do comando db. Por padrão, o banco de dados é especificado por `db` em Componentes de Aplicação.

Eventualmente, você desejará aplicar apenas uma ou algumas migrações, em vez de todas as disponíveis. Você pode fazê-lo especificando o número de migrações que deseja aplicar ao executar o comando. Por exemplo, o comando a seguir tentará aplicar as próximas 3 migrações disponíveis:

    yii migrate 3

Você também pode especificar para qual migração em particular o banco de dados deve ser migrado usando o comando `migrate/to` em um dos formatos seguintes:

    yii migrate/to 150101_185401                        # usando a marcação de data para especificar a migração
    yii migrate/to "2015-01-01 18:54:01"                # usando uma string que pode ser analisada por strtotime()
    yii migrate/to m150101_185401_criar_tabela_noticias # usando o nome completo
    yii migrate/to 1392853618                           # usando uma marcação de data no estilo UNIX

Se existirem migrações mais recentes do que a especificada, elas serão todas aplicadas antes da migração definida.

Se a migração especificada já tiver sido aplicada, qualquer migração posterior já aplicada será revertida.

## Revertendo Migrações ¶

Para reverter uma ou múltiplas migrações que tenham sido aplicadas antes, você pode executar o seguinte comando:

    yii migrate/down     # reverter a última migração aplicada
    yii migrate/down 3   # reverter as 3 últimas migrações aplicadas

> Observação: Nem todas as migrações são reversíveis. Tentar reverter tais migrações causará um erro que cancelará todo o processo de reversão.

## Refazendo Migrações ¶

Refazer as migrações significa primeiramente reverter migrações especificadas e depois aplicá-las novamente. Isto pode ser feito da seguinte maneira:

    yii migrate/redo        # refazer a última migração aplicada
    yii migrate/redo 3      # refazer as 3 últimas migrações aplicadas

> Observação: Se a migração não for reversível, você não poderá refazê-la.

## Listando Migrações ¶

Para listar quais migrações foram aplicadas e quais não foram, você deve usar os seguintes comandos:

    yii migrate/history     # exibir as 10 últimas migrações aplicadas
    yii migrate/history 5   # exibir as 5 últimas migrações aplicadas
    yii migrate/history all # exibir todas as migrações aplicadas

    yii migrate/new         # exibir as 10 primeiras novas migrações
    yii migrate/new 5       # exibir as 5 primeiras novas migrações
    yii migrate/new all     # exibir todas as novas migrações

## Modificando o Histórico das Migrações ¶

Ao invés de aplicar ou reverter migrações, pode ser que você queira apenas definir que o seu banco de dados foi atualizado para uma migração em particular. Isto normalmente acontece quando você muda manualmente o banco de dados para um estado em particular, e não deseja que as mudanças para aquela migração sejam reaplicadas posteriormente. Você pode alcançar este objetivo com o seguinte comando:

    yii migrate/mark 150101_185401                        # usando a marcação de data para especificar a migração
    yii migrate/mark "2015-01-01 18:54:01"                # usando uma string que pode ser analisada por strtotime()
    yii migrate/mark m150101_185401_criar_tabela_noticias # usando o nome completo
    yii migrate/mark 1392853618                           # usando uma marcação de data no estilo UNIX

O comando modificará a tabela `migration` adicionando ou deletando certos registros para indicar que o banco de dados sofreu as migrações especificadas. Nenhuma migração será aplicada ou revertida por este comando.

## Customizando Migrações ¶

Existem várias maneiras de customizar o comando de migração.

### Usando Opções na Linha de Comando ¶

O comando de migração vem com algumas opções de linha de comando que podem ser usadas para customizar o seu comportamento:

- `interactive`: boolean (o padrão é `true`), especifica se as migrações serão executadas em modo interativo. Quando for `true`, ao usuário será perguntado se a execução deve continuar antes de o comando executar certas ações. Você provavelmente marcará isto para falso se o comando estiver sendo feito em algum processo em segundo plano.

- `migrationPath`: string (o padrão é `@app/migrations`), especifica o diretório em que os arquivos das classes de migração estão. Isto pode ser especificado ou como um diretório ou como um alias. Observe que o diretório deve existir, ou o comando disparará um erro.

- `migrationTable`: string (o padrão é `migration`), especifica o nome da tabela no banco de dados para armazenar o histórico das migrações. A tabela será automaticamente criada pelo comando caso não exista. Você também pode criá-la manualmente usando a estrutura `version varchar(255) primary key, apply_time integer`.

- `db`: string (o padrão é `db`), especifica o banco de dados do componente de aplicação. Representa qual banco sofrerá as migrações usando este comando.

- `templateFile`: string (o padrão é `@yii/views/migration.php`), especifica o caminho do arquivo de modelo que é usado para gerar um esqueleto para os arquivos das classes de migração. Isto pode ser especificado por um caminho de arquivo ou por um alias. O arquivo modelo é um script PHP em que você pode usar uma variával pré-definida `$className` para obter o nome da classe de migração.

O seguinte exemplo exibe como você pode usar estas opções.

Por exemplo, se nós quisermos migrar um módulo `forum` cujo os arquivos de migração estão localizados dentro da pasta `migrations` do módulo, nós podemos usar o seguinte comando:

    # migrate the migrations in a forum module non-interactively
    yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0

### Configurando o Comando Globalmente ¶

Ao invés de fornecer opções todas as vezes que você executar o comando de migração, você pode configurá-lo de uma vez por todas na configuração da aplicação como exibido a seguir:

    return [
        'controllerMap' => [
            'migrate' => [
                'class' => 'yii\console\controllers\MigrateController',
                'migrationTable' => 'backend_migration',
            ],
        ],
    ];

Com a configuração acima, toda a vez que você executar o comando de migração, a tabela `backend_migration` será usada para gravar o histórico de migração. Você não precisará mais fornecê-la através da opção `migrationTable`.

## Migrando Múltiplos Bancos De Dados ¶

Por padrão, as migrações são aplicadas no mesmo banco de dados especificado por `db` do componente de aplicação. Se você quiser que elas sejam aplicadas em um banco de dados diferente, você deve especificar na opção `db` como exibido a seguir:

    yii migrate --db=db2

O comando acima aplicará as migrações para o banco de dados `db2`.

Algumas vezes pode ocorrer que você queira aplicar algumas das migrações para um banco de dados, e outras para outro banco de dados. Para atingir este objetivo, ao implementar uma classe de migração você deve especificar a ID do componente DB que a migração usará, como o seguinte:

    use yii\db\Schema;
    use yii\db\Migration;

    class m150101_185401_criar_tabela_noticias extends Migration
    {
        public function init()
        {
            $this->db = 'db2';
            parent::init();
        }
    }

A migração acima será aplicada a `db2`, mesmo que você especifique um banco de dados diferente através da opção `db`. Observe que o histórico da migração continuará sendo registrado no banco especificado pela opção `db`. Se você tiver múltiplas migrações que usam o mesmo banco de dados, é recomenda-se criar uma classe de migração base com o código acima em `init()`. Então cada classe de migração poderá ser estendida desta classe base.

> Dica: Apesar de definir a propriedade db, você também pode operar em diferentes bancos de dados ao criar novas conexões de banco para eles em sua classe de migração. Você então usará os métodos DAO com estas conexões para manipular diferentes bancos de dados.

Outra estratégia que você pode seguir para migrar múltiplos bancos de dados é manter as migrações para diferentes bancos de dados em diferentes pastas de migrações. Então você poderá migrar estes bancos de dados em comandos separados como os seguintes:

    yii migrate --migrationPath=@app/migrations/db1 --db=db1
    yii migrate --migrationPath=@app/migrations/db2 --db=db2
    ...

O primeiro comando aplicará as migrações em `@app/migrations/db1` para o banco de dados `db1`, e o segundo comando aplicará as migrações em `@app/migrations/db2` para `db2`, e assim sucessivamente.