Migrations

Migrations é um plugin suportado pela equipe oficial do CakePHP que ajuda você a fazer mudanças no schema do banco de dados utilizando arquivos PHP, que podem ser versionados utilizando um sistema de controle de versão.

Ele permite que você atualize suas tabelas ao longo do tempo. Ao invés de escrever modificações de schema via SQL, este plugin permite que você utilize um conjunto intuitivo de métodos para fazer mudanças no seu banco de dados.

Esse plugin é um wrapper para a biblioteca Phinx.

Instalação

Por padrão o plugin é instalado junto com o esqueleto da aplicação. Se você o removeu e quer reinstalá-lo, execute o comando a seguir a partir do diretório ROOT da sua aplicação (onde o arquivo composer.json está localizado):

$ php composer.phar require cakephp/migrations "@stable"

// Or if composer is installed globally

$ composer require cakephp/migrations "@stable"

Para usar o plugin você precisa carregá-lo no arquivo config/bootstrap.php da sua aplicação. Você pode usar o shell de plugins do CakePHP para carregar e descarregar plugins do seu arquivo config/bootstrap.php:

$ bin/cake plugin load Migrations

Ou você pode carregar o plugin editando seu arquivo config/bootstrap.php e adicionando a linha:

Plugin::load('Migrations');

Adicionalmente, você precisará configurar o banco de dados padrão da sua aplicação, no arquivo config/app.php como explicado na seção Configuração de banco de dados.

Visão Geral

Uma migração é basicamente um arquivo PHP que descreve as mudanças a serem feitas no banco de dados. Um arquivo de migração pode criar ou excluir tabelas, adicionar ou remover colunas, criar índices e até mesmo inserir dados em seu banco de dados.

Aqui segue um exemplo de migração:

<?php
use Migrations\AbstractMigration;

class CreateProducts extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('description', 'text', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->create();
    }
}

Essa migração irá adicionar uma tabela chamada products ao banco de dados com as seguintes colunas:

  • id coluna do tipo integer como chave primária
  • name coluna do tipo string
  • description coluna do tipo text
  • created coluna do tipo datetime
  • modified coluna do tipo datetime

Dica

A coluna de chave primária id será adicionada implicitamente.

Nota

Note que este arquivo descreve como o banco de dados deve ser após a aplicação da migração. Neste ponto, a tabela products``ainda não existe no banco de dados, nós apenas criamos um arquivo que é capaz de criar a tabela ``products com seus devidos campos ou excluir a tabela caso uma operação rollback seja executada.

Com o arquivo criado na pasta config/MIgrations, você será capaz de executar o comando abaixo para executar as migrações no seu banco de dados:

bin/cake migrations migrate

O comando seguinte irá executar um rollback na migração e irá excluir a tabela recém criada:

bin/cake migrations rollback

Criando migrations

Arquivos de migração são armazeados no diretório config/Migrations da sua aplicação. O nome dos arquivos de migração têm como prefixo a data em que foram criados, no formato YYYYMMDDHHMMSS_MigrationName.php. Aqui estão exemplos de arquivos de migração:

  • 20160121163850_CreateProducts.php
  • 20160210133047_AddRatingToProducts.php

A maneira mais fácil de criar um arquivo de migrações é usando o Geração de código com bake a linha de comando.

Por favor, leia a documentação do Phinx <http://docs.phinx.org/en/latest/migrations.html> a fim de conhecer a lista completa dos métodos que você pode usar para escrever os arquivos de migração.

Nota

Ao gerar as migrações através do bake você ainda pode alterá-las antes da sua execução, caso seja necessário.

Sintaxe

A sintaxe do bake para a geração de migrações segue o formato abaixo:

$ bin/cake bake migration CreateProducts name:string description:text created modified

Quando utilizar o bake para criar as migrações, você normalmente precisará informar os seguintes dados:

* o nome da migração que você irá gerar (``CreateProducts`` por exemplo)
* as colunas da tabela que serão adicionadas ou removidas na migração
(``name:string description:text created modified`` no nosso caso)

Devido às convenções, nem todas as alterações de schema podem ser realizadas através destes comandos.

Além disso, você pode criar um arquivo de migração vazio caso deseje ter um controle total do que precisa ser executado. Para isto, apenas omita a definição das colunas:

$ bin/cake migrations create MyCustomMigration

Nomenclatura de migrations

A nomenclatura das migrações pode seguir qualquer um dos padrões apresentados a seguir:

  • (/^(Create)(.*)/) Cria a tabela especificada.
  • (/^(Drop)(.*)/) Exclui a tabela especificada. Ignora campos especificados nos argumentos
  • (/^(Add).*(?:To)(.*)/) Adiciona campos a tabela especificada
  • (/^(Remove).*(?:From)(.*)/) Remove campos de uma tabela específica
  • (/^(Alter)(.*)/) Altera a tabela especificada. Um apelido para um CreateTable seguido de um AlterTable

Você também pode usar underscore_form como nome das suas migrations. Ex.: create_products.

Novo na versão cakephp/migrations: 1.5.2

A partir da versão 1.5.2 do plugin migrations, o nome dos arquivos de migrações são colocados automaticamente no padrão camel case. Esta versão do plugin está disponível apenas a partir da versão 3.1 do CakePHP. Antes disto, o padrão de nomes do plugin migrations utilizava a nomenclatura baseada em underlines, ex.: 20160121164955_create_products.php.

Aviso

O nome das migrações são usados como nomes de classe, e podem colidir com outras migrações se o nome das classes não forem únicos. Neste caso, pode ser necessário sobreescrever manualmente os nomes mais tarde ou simplesmente mudar os nomes que você está especificando.

Definição de colunas

Quando utilizar colunas na linha de comando, pode ser útil lembrar que eles seguem o seguinte padrão:

fieldName:fieldType[length]:indexType:indexName

Por exemplo, veja formas válidas de especificar um campo de e-mail:

  • email:string:unique
  • email:string:unique:EMAIL_INDEX
  • email:string[120]:unique:EMAIL_INDEX

O parâmetro length para o fieldType é opcional e deve sempre ser escrito entre colchetes

Os campos created e modified serão automaticamente definidos como datetime.

Os tipos de campos são genericamente disponibilizados pela biblioteca Phinx. Eles podem ser:

  • string
  • text
  • integer
  • biginteger
  • float
  • decimal
  • datetime
  • timestamp
  • time
  • date
  • binary
  • boolean
  • uuid

Há algumas heurísticas para a escolha de tipos de campos que não são especificados ou são definidos com valor inválido. O tipo de campo padrão é string;

  • id: integer
  • created, modified, updated: datetime

Criando uma tabela

Você pode utilizar o bake para criar uma tabela:

$ bin/cake bake migration CreateProducts name:string description:text created modified

A linha de comando acima irá gerar um arquivo de migração parecido com este:

<?php
use Migrations\AbstractMigration;

class CreateProducts extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('description', 'text', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->create();
    }
}

Adicionando colunas a uma tabela existente

Se o nome da migração na linha de comando estiver na forma “AddXXXToYYY” e for seguido por uma lista de nomes de colunas e tipos, então o arquivo de migração com o código para criar as colunas será gerado:

$ bin/cake bake migration AddPriceToProducts price:decimal

A linha de comando acima irá gerar um arquivo com o seguinte conteúdo:

<?php
use Migrations\AbstractMigration;

class AddPriceToProducts extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('price', 'decimal')
              ->update();
    }
}

Adicionando uma coluna como indice a uma tabela

Também é possível adicionar índices a colunas:

$ bin/cake bake migration AddNameIndexToProducts name:string:index

irá gerar:

<?php
use Migrations\AbstractMigration;

class AddNameIndexToProducts extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('name', 'string')
              ->addIndex(['name'])
              ->update();
    }
}

Especificando o tamanho do campo

Novo na versão cakephp/migrations: 1.4

Se você precisar especificar o tamanho do campo, você pode fazer isto entre colchetes logo após o tipo do campo, ex.:

$ bin/cake bake migration AddFullDescriptionToProducts full_description:string[60]

Executar o comando acima irá gerar:

<?php
use Migrations\AbstractMigration;

class AddFullDescriptionToProducts extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('full_description', 'string', [
            'default' => null,
            'limit' => 60,
            'null' => false,
        ])
        ->update();
    }
}

Se o tamanho não for especificado, os seguintes padrões serão utilizados:

  • string: 255
  • integer: 11
  • biginteger: 20

Removendo uma coluna de uma tabela

Da mesma forma, você pode gerar uma migração para remover uma coluna utilizando a linha de comando, se o nome da migração estiver na forma “RemoveXXXFromYYY”:

$ bin/cake bake migration RemovePriceFromProducts price

Cria o arquivo:

<?php
use Migrations\AbstractMigration;

class RemovePriceFromProducts extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('products');
        $table->removeColumn('price');
    }
}

Gerando migrações a partir de uma base de dados existente

Se você está trabalhando com um banco de dados pré-existente e quer começar a usar migrações, ou para versionar o schema inicial da base de dados da sua aplicação, você pode executar o comando migration_snapshot:

$ bin/cake bake migration_snapshot Initial

Isto irá gerar um arquivo de migração chamado YYYYMMDDHHMMSS_Initial.php contendo todas as instruções CREATE para todas as tabelas no seu banco de dados.

Por padrão, o snapshot será criado a partir da conexão default definida na configuração. Se você precisar fazer o bake de um snapshot de uma fonte de dados diferente, você pode utilizar a opção --connection:

$ bin/cake bake migration_snapshot Initial --connection my_other_connection

Você também pode definir que o snapshot inclua apenas as tabelas para as quais você tenha definido models correspendentes, utilizando a flag require-table:

$ bin/cake bake migration_snapshot Initial --require-table

Quando utilizar a flag --require-table, o shell irá olhar através das classes do diretório Table da sua aplicação e apenas irá adicionar no snapshot as tabelas lá definidas.

A mesma lógica será aplicada implicitamente se você quiser fazer o bake de um snapshot para um plugin. Para fazer isso, você precisa usar a opção --plugin, veja a seguir:

$ bin/cake bake migration_snapshot Initial --plugin MyPlugin

Apenas as tabelas que tiverem um objeto Table definido serão adicionadas ao snapshot do seu plugin.

Nota

Quando fizer o bake de um snapshot para um plugin, os arquivos de migrações serão criados no diretório config/Migrations do seu plugin.

Fique atento que quando você faz o bake de um snapshot, ele é automaticamente
adicionado ao log do phinx como migrado.

Os Comandos

migrate : Aplicando migrações

Depois de ter gerado ou escrito seu arquivo de migração, você precisa executar o seguinte comando para aplicar as mudanças a sua base de dados:

# Executa todas as migrações
$ bin/cake migrations migrate

# Execute uma migração específica utilizando a opção ``--target`` ou ``-t``
# O valor é um timestamp que serve como prefixo para cada migração::
$ bin/cake migrations migrate -t 20150103081132

# Por padrão, as migrações ficam no diretório **config/Migrations**. Você
# pode especificar um diretório utilizando a opção ``--source`` ou ``-s``.
# O comando abaixo executa as migrações no diretório **config/Alternate**
$ bin/cake migrations migrate -s Alternate

# Você pode executar as migrações de uma conexão diferente da ``default``
# utilizando a opção ``--connection`` ou ``-c``.
$ bin/cake migrations migrate -c my_custom_connection

# Migrações também podem ser executadas para plugins. Simplesmente utilize
# a opção ``--plugin`` ou ``-p``
$ bin/cake migrations migrate -p MyAwesomePlugin

rollback : Revertendo migrações

O comando rollback é utilizado para desfazer migrações realizadas anteriormente pelo plugin Migrations. É o inverso do comando migrate:

# Você pode desfazer uma migração anterior utilizando o
# comando ``rollback``::
$ bin/cake migrations rollback

# Você também pode passar a versão da migração para voltar
# para uma versão específica::
$ bin/cake migrations rollback -t 20150103081132

Você também pode utilizar as opções --source, --connection e --plugin exatamente como no comando migrate.

status : Status da migração

O comando status exibe uma lista de todas as migrações juntamente com seu status. Você pode utilizar este comando para ver quais migrações foram executadas:

$ bin/cake migrations status

Você também pode ver os resultados como JSON utilizando a opção --format (ou -f):

$ bin/cake migrations status --format json

Você também pode utilizar as opções --source, --connection e --plugin exatamente como no comando migrate.

mark_migrated : Marcando uma migração como migrada

Novo na versão 1.4.0.

Algumas vezes pode ser útil marcar uma lista de migrações como migrada sem efetivamente executá-las. Para fazer isto, você pode usar o comando mark_migrated. O comando é bastante semelhante aos outros comandos.

Você pode marcar todas as migrações como migradas utilizando este comando:

$ bin/cake migrations mark_migrated

Você também pode marcar todas as migrações de uma versão específica utilizando a opção --target:

$ bin/cake migrations mark_migrated --target=20151016204000

Se você não quer marcar a migração alvo como migrada durante o processo, você pode utilizar a opção --exclude:

$ bin/cake migrations mark_migrated --target=20151016204000 --exclude

Finalmente, se você deseja marcar somente a migração alvo como migrada, você pode utilizar a opção --only:

$ bin/cake migrations mark_migrated --target=20151016204000 --only

Você também pode utilizar as opções --source, --connection e --plugin exatamente como no comando migrate.

Nota

Quando você criar um snapshot utilizando o bake com o comando cake bake migration_snapshot, a migração criada será automaticamente marcada como migrada.

Obsoleto desde a versão 1.4.0: A seguinte maneira de utilizar o comando foi depreciada. Use somente se você estiver utilizando uma versão do plugin inferior a 1.4.0.

Este comando espera um número de versão de migração como argumento:

$ bin/cake migrations mark_migrated

Se você deseja marcar todas as migrações como migradas, você pode utilizar o valor especial all. Se você o utilizar, ele irá marcar todas as migrações como migradas:

$ bin/cake migrations mark_migrated all

seed : Populando seu banco de dados

A partir da versão 1.5.5, você pode usar a shell de migrations para popular seu banco de dados. Essa função é oferecida graças ao recurso de seed da biblioteca Phinx. Por padrão, arquivos seed ficarão no diretório config/Seeds de sua aplicação. Por favor, tenha certeza de seguir as instruções do Phinx para construir seus arquivos de seed.

Assim como nos migrations, uma interface do `bake é oferecida para gerar arquivos de seed:

# This will create a ArticlesSeed.php file in the directory config/Seeds of your application
# By default, the table the seed will try to alter is the "tableized" version of the seed filename
$ bin/cake bake seed Articles

# You specify the name of the table the seed files will alter by using the ``--table`` option
$ bin/cake bake seed Articles --table my_articles_table

# You can specify a plugin to bake into
$ bin/cake bake seed Articles --plugin PluginName

# You can specify an alternative connection when generating a seeder.
$ bin/cake bake seed Articles --connection connection

Para popular seu banco de dados, você pode usar o subcomando seed:

# Without parameters, the seed subcommand will run all available seeders
# in the target directory, in alphabetical order.
$ bin/cake migrations seed

# You can specify only one seeder to be run using the `--seed` option
$ bin/cake migrations seed --seed ArticlesSeed

# You can run seeders from an alternative directory
$ bin/cake migrations seed --source AlternativeSeeds

# You can run seeders from a plugin
$ bin/cake migrations seed --plugin PluginName

# You can run seeders from a specific connection
$ bin/cake migrations seed --connection connection

Esteja ciente que, ao oposto das migrations, seeders não são versionados, o que significa que o mesmo seeder pode ser aplicado diversas vezes.

Usando migrations em plugins

Plugins também podem oferecer migrations. Isso faz com que plugins que são planejados para serem distribuídos tornem-se muito mais práticos e fáceis de instalar. Todos os comandos do plugin Migrations suportam a opção --plugin ou -p, que por sua vez vai delegar a execução da tarefa ao escopo relativo a um determinado plugin:

$ bin/cake migrations status -p PluginName

$ bin/cake migrations migrate -p PluginName

Executando migrations em ambientes fora da linha de comando

Novo na versão cakephp/migrations: 1.2.0

Desde o lançamento da versão 1.2 do plugin, você pode executar migrations fora da linha de comando, diretamente de uma aplicação, ao usar a nova classe Migrations. Isso pode ser muito útil caso você esteja desenvolvendo um instalador de plugins para um CMS, para exemplificar.

A classe Migrations permite que você execute os seguintes comandos disponíveis na shell:

  • migrate
  • rollback
  • markMigrated
  • status
  • seed

Cada um desses comandos tem um método definido na classe Migrations.

Veja como usá-la:

use Migrations\Migrations;

$migrations = new Migrations();

// Retornará um array de todos migrations e seus status
$status = $migrations->status();

// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
$migrate = $migrations->migrate();

// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
$rollback = $migrations->rollback();

// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
$markMigrated = $migrations->markMigrated(20150804222900);

// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
$seeded = $migrations->seed();

Os métodos aceitam um array de parâmetros que devem combinar com as opções dos comandos:

use Migrations\Migrations;

$migrations = new Migrations();

// Retornará um array de todos migrations e seus status
$status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);

Você pode passar qualquer opção que esteja disponível pelos comandos shell. A única exceção é o comando markMigrated que espera um número de versão a ser marcado como migrado, como primeiro argumento. Passe o array de parâmetros como segundo argumento nesse caso.

Opcionalmente, você pode passar esses parâmetros pelo construtor da classe. Eles serão usados como padrão evitando que você tenha que passá-los em cada chamada do método:

use Migrations\Migrations;

$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);

// Todas as chamadas de métodos serão executadas usando os parâmetros passados pelo construtor da classe
$status = $migrations->status();
$migrate = $migrations->migrate();

Se você precisar sobrescrever um ou mais parâmetros definidos previamente, você pode passá-los para um método:

use Migrations\Migrations;

$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);

// Essa chamada será feita com a conexão "custom"
$status = $migrations->status();
// Essa chamada será feita com a conexão "default"
$migrate = $migrations->migrate(['connection' => 'default']);

Dicas e truques

Criando chaves primárias customizadas

Se você precisa evitar a criação automática da chave primária id ao adicioanr novas tabelas ao banco de dados, é possível usar o segundo argumento do método table():

<?php
use Migrations\AbstractMigration;

class CreateProductsTable extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('products', ['id' => false, 'primary_key' => ['id']]);
        $table
              ->addColumn('id', 'uuid')
              ->addColumn('name', 'string')
              ->addColumn('description', 'text')
              ->create();
    }
}

O código acima vai criar uma coluna CHAR(36) id que também é a chave primária.

Nota

Ao especificar chaves primárias customizadas pela linha de comando, você deve apontá-las como chave primária no campo id, caso contrário você pode receber um erro apontando campos diplicados, i.e.:

$ bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified

Adicionalmente, desde a versão 1.3, uma novo meio de lidar com chaves primárias foi introduzido. Para tal, sua classe de migração deve estender a nova classe Migrations\AbstractMigration.

Você pode especificar uma propriedade autoId na sua classe e defini-la como false, o quê desabilitará a geração automática da coluna id. Você vai precisar criar manualmente a coluna que será usada como chave primária e adicioná-la à declaração da tabela:

<?php
use Migrations\AbstractMigration;

class CreateProductsTable extends AbstractMigration
{

    public $autoId = false;

    public function up()
    {
        $table = $this->table('products');
        $table
            ->addColumn('id', 'integer', [
                'autoIncrement' => true,
                'limit' => 11
            ])
            ->addPrimaryKey('id')
            ->addColumn('name', 'string')
            ->addColumn('description', 'text')
            ->create();
    }
}

Comparado ao método apresentado anteriormente de lidar com chaves primárias, nesse método, temos a possibilidade de ter maior controle sobre as definições da coluna da chave primária: unsigned, limit, comentários, etc.

Todas as migrations e snapshots criadas pelo bake vão usar essa nova forma quando necessário.

Aviso

Lidar com chaves primárias só é possível no momento de criação de tabelas. Isso é devido a algumas limitações de alguns servidores de banco de dados que o plugin suporta.

Colações

Se você precisar criar uma tabela com colação diferente do padrão do banco de dados, você pode defini-la pelo método table(), como uma opção:

<?php
use Migrations\AbstractMigration;

class CreateCategoriesTable extends AbstractMigration
{
    public function change()
    {
        $table = $this
            ->table('categories', [
                'collation' => 'latin1_german1_ci'
            ])
            ->addColumn('title', 'string', [
                'default' => null,
                'limit' => 255,
                'null' => false,
            ])
            ->create();
    }
}

Note que isso só pode ser feito na criação da tabela : não há atualmente uma forma de adicionar uma coluna a uma tabela existente com uma colação diferente do padrão da tabela, ou mesmo do banco de dados. Apenas MySQL e SqlServer suportam essa chave de configuração.

Atualizando nome de colunas e usando objetos de tabela

Se você usa um objeto ORM Table do CakePHP para manipular valores do seu banco de dados, renomeando ou removendo uma coluna, certifique-se de criar uma nova instância do seu objeto depois da chamada do update(). O registro do objeto é limpo depois da chamada do update() para atualizar o schema que é refletido e armazenado no objeto Table paralelo à instanciação.

Migrations e Deployment

Se você usa o plugin ao fazer o deploy de sua aplicação, garanta que o cache ORM seja limpo para renovar os metadados das colunas de suas tabelas. Caso contrário, você pode acabar recebendo erros relativos a colunas inexistentes ao criar operações nessas mesmas colunas. O core do CakePHP possui uma ORM Cache Shell que você pode usar para realizar essas operação:

$ bin/cake orm_cache clear

Leia a seção ORM Cache Shell do cookbook se você quiser conhecer mais sobre essa shell.