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 ``` 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 _`, onde * `` refere-se a data UTC em que o comando de criação da migração foi executado. * `` é 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`: ```php 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 [[yii\db\Migration::db|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](db-dao.md#database-schema). 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_`. 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 [[yii\db\QueryBuilder::$typeMap|$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](db-dao.md#performing-transactions-). 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. ```php 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](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html). 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](db-dao.md) 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: * [[yii\db\Migration::execute()|execute()]]: executando um SQL * [[yii\db\Migration::insert()|insert()]]: inserindo um novo registro * [[yii\db\Migration::batchInsert()|batchInsert()]]: inserindo vários registros * [[yii\db\Migration::update()|update()]]: atualizando registros * [[yii\db\Migration::delete()|delete()]]: apagando registros * [[yii\db\Migration::createTable()|createTable()]]: criando uma tabela * [[yii\db\Migration::renameTable()|renameTable()]]: renomeando uma tabela * [[yii\db\Migration::dropTable()|dropTable()]]: removendo uma tabela * [[yii\db\Migration::truncateTable()|truncateTable()]]: removendo todos os registros em uma tabela * [[yii\db\Migration::addColumn()|addColumn()]]: adicionando uma coluna * [[yii\db\Migration::renameColumn()|renameColumn()]]: renomeando uma coluna * [[yii\db\Migration::dropColumn()|dropColumn()]]: removendo uma coluna * [[yii\db\Migration::alterColumn()|alterColumn()]]: alterando uma coluna * [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: adicionando uma chave primária * [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: removendo uma chave primária * [[yii\db\Migration::addForeignKey()|addForeignKey()]]: adicionando uma chave estrangeira * [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: removendo uma chave estrangeira * [[yii\db\Migration::createIndex()|createIndex()]]: criando um índice * [[yii\db\Migration::dropIndex()|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](db-query-builder.md) 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 [[yii\console\controllers\MigrateController::db|db]]. Por padrão, o banco de dados é especificado por `db` em [Componentes de Aplicação](structure-application-components.md). 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](concept-aliases.md). 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](structure-application-components.md). 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](concept-aliases.md). 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: ```php 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](structure-application-components.md). 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: ```php 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 [[yii\db\Migration::db|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](db-dao.md) 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.