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:
- João cria uma nova migração (ex. cria uma nova tabela, muda a definição de uma coluna, etc.).
- João comita a nova migração no sistema de controle de versão (ex. Git, Mercurial).
- Pedro atualiza seu repositório a partir do sistema de controle de versão e recebe a nova migração.
- 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:
- Luiz cria uma nova tag para o repositório do projeto que contem algumas novas migrações de dados.
- Luiz atualiza o código fonte no servidor em produção para a tag criada.
- 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.
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 argumentonome
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 mente não será capaz de recuperar este registro com o métododown()
. Em alguns casos, você pode ter tido muita preguiça e não ter implementado o métododown()
, porque não é muito comum reverter migrações de dados. Neste caso, você deve retornarfalse
no métododown()
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.
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 [[yii\db\QueryBuilder::$typeMap|$typeMap]] em cada classe
QueryBuilder
.
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()
edown()
.
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:
- [[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 para construir e executar consultas complexas.
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 pordb
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.
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.
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.
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
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.
Existem várias maneiras de customizar o comando de migração.
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 fortrue
, 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 estruturaversion 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
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
.
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 [[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 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.