Como automatizar alterações no banco de dados com Phinx e DevOps
Em tempos que se fala tanto de DevOps quero te mostrar como utilizar o Phinx para efetuar alterações no seu banco de dados à medida que a sua aplicação evolui, possibilitando que você automatize esse processo com uma ferramenta de integração contínua logo em seguida.
Imagine a seguinte situação: no seu ambiente de desenvolvimento você criou sua aplicação em PHP bem como o seu banco de dados. Logo em seguida você o colocou em produção. Até aí está tudo lindo, maravilhoso. Dando continuidade ao seu projeto você percebeu a necessidade de criar uma tabela nova. Ok, sem problemas, no seu ambiente de desenvolvimento você cria a bendita tabela. Mas como você procede para fazer isso em produção?
Talvez você não tenha percebido o problema agora, mas no futuro e provavelmente bem próximo, conforme as demandas forem aumentando, fica complicado administrar essas alterações do banco de dados em produção. E se você tiver um ambiente de homologação então, fazer a mesma alteração no banco de dados 3 vezes, já pensou?
Se você passa por esse tipo de situação quero lhe apresentar o Phinx.
O que é o Phinx?
De acordo com a documentação, o Phinx é uma ferramenta via linha de comando para gerenciamento de migrações de banco de dados. Com ele você poderá escrever scripts PHP para efetuar as alterações que você necessita no seu banco de dados e gerenciar isso via linha de comando.
Alguns frameworks fullstack de PHP tem recurso para migrations, inclusive o do CakePHP é baseado no próprio Phinx, mas vale a pena falar dele ou talvez porque você não esteja usando framework, ou se trata de código legado com um framework sem esse recurso ou simplesmente pela facilidade que o Phinx proporciona.
Instalação
A instalação pode ser feita via composer:
01 | juciellen@cabrera:~/Documentos/projetos/expressive$ php composer.phar require robmorgan/phinx |
02 | Using version ^0.9.1 for robmorgan/phinx |
03 | ./composer.json has been updated |
04 | Loading composer repositories with package information |
05 | Updating dependencies (including require-dev) |
06 | Package operations: 3 installs, 0 updates, 0 removals |
07 | - Installing symfony/filesystem (v3.3.13): Downloading (100%) |
08 | - Installing symfony/config (v3.3.13): Downloading (100%) |
09 | - Installing robmorgan/phinx (v0.9.1): Downloading (100%) |
11 | Generating autoload files |
A seguir crie os diretórios db/migrations para armazenar as suas migrations não esquecendo de dar as devidas permissões. Feito isso execute o comando abaixo no terminal:
1 | juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/bin/phinx init |
Repare que o arquivo phinx.yml foi criado na raiz do projeto. Esse é o arquivo de configuração do phinx.
Configurando o Phinx
Abaixo um modelo enxuto de phinx.yml.
02 | migrations: %%PHINX_CONFIG_DIR%%/db/migrations |
05 | default_migration_table: phinxlog |
06 | default_database: development |
17 | version_order: creation |
Neste arquivo você precisará indicar os diretórios das suas migrations em paths, no nosso caso db/migrations.
Em environments deve conter as configurações para conexão com banco de dados em todos os ambientes em que você executará suas migrations. Eu deixei apenas as configurações do meu ambiente de desenvolvimento mas você pode acrescentar quantos blocos forem necessários, como homolog, production e etc. Eu estou usando Postgres, por isso o adapter é pgsql, mas consulte o manual do phinx para indicar o adapter de acordo com o SGDB que você estiver usando.
Escrevendo migrations
Para criar uma migration você deve executar o seguinte comando no terminal, sempre a partir do diretório que estiver o seu phinx.yml
1 | juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx create MinhaPrimeiraMigration |
O padrão de nome de migration é CamelCase. O resultado é o seguinte:
03 | using config file ./phinx.yml |
04 | using config parser yaml |
06 | - /home/juciellen/Documentos/projetos/expressive/db/migrations |
08 | using migration base class Phinx\Migration\AbstractMigration |
10 | created db/migrations/20171124233848_minha_primeira_migration.php |
Repare que o arquivo 20171124233848_minha_primeira_migration.php foi criado no diretório db/migrations, que indicamos na configuração. É nesse arquivo que escreveremos nossa migration.
02 | use Phinx\Migration\AbstractMigration; |
04 | class MinhaPrimeiraMigration extends AbstractMigration |
27 | public function change() |
Notem que o arquivo já vem com um método chamado change. É dentro desse método que você vai escrever o que vai ocorrer quando a migration for executada (você pode usar o método up para escrever a migration, mais a frente explico a diferença).
Vamos escrever uma migration que cria a tabela users contendo os campos id, name, email, password, active e created.
01 | public function change() |
04 | ->addColumn( 'name' , 'string' ) |
05 | ->addColumn( 'email' , 'string' , [ |
08 | ->addColumn( 'password' , 'string' , [ |
11 | ->addColumn( 'active' , 'boolean' , [ |
15 | ->addColumn( 'created' , 'datetime' ,[ 'default' => 'CURRENT_TIMESTAMP' ]) |
Reparem que não criei o campo id, porque o phinx vai fazer isso automaticamente ao executar a migration.
Utilizei alguns métodos que vou explicar melhor:
table -> passando como parâmetro users, que é o nome da tabela que desejo trabalhar.
addColumn -> passando como parâmetros o nome da nova coluna, o tipo e as opções. Em opções é onde você pode indicar se o campo pode ser nulo, definir um valor default e por aí vai.
create -> cria a tabela
Executando migrations
Para executar as migrations você deve executar o comando migrate. Caso você precise executar a migration em mais de um ambiente não esqueça de indicá-lo, no meu caso migrate -e development.
01 | juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx migrate -e development |
04 | using config file ./phinx.yml |
05 | using config parser yaml |
07 | - /home/juciellen/Documentos/projetos/expressive/db/migrations |
09 | using environment development |
13 | == 20171124233848 MinhaPrimeiraMigration: migrating |
14 | == 20171124233848 MinhaPrimeiraMigration: migrated 0.1273s |
Após a execução a tabela foi criada, bem como a respectiva sequence. Simples não?
03 | Schema | Name | Type | Owner |
05 | public | phinxlog | table | jucabrera |
06 | public | users | table | jucabrera |
07 | public | users_id_seq | sequence | jucabrera |
12 | Column | Type | Modifiers |
14 | id | integer | not null default nextval( 'users_id_seq' ::regclass) |
15 | name | character varying (255) | not null |
16 | email | character varying (255) | not null |
17 | password | character varying (255) | not null |
18 | active | boolean | not null default true |
19 | created | timestamp without time zone | not null default now() |
21 | "users_pkey" PRIMARY KEY , btree (id) |
O phinx gerencia as migrations executadas utilizando a tabela phinxlog criada por ele automaticamente no seu banco de dados, de modo que uma vez executada a migration, ela não será executada novamente.
Você pode acompanhar isso através do comando status.
01 | juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx status -e development |
04 | using config file ./phinx.yml |
05 | using config parser yaml |
07 | - /home/juciellen/Documentos/projetos/expressive/db/migrations |
09 | using environment development |
10 | ordering by creation time |
12 | Status [Migration ID] Started Finished Migration Name |
13 | ---------------------------------------------------------------------------------- |
14 | up 20171124233848 2017-11-24 22:41:47 2017-11-24 22:41:47 MinhaPrimeiraMigration |
Rollback
Pode ocorrer de você perceber que esqueceu de criar algum campo, por exemplo, e queira desfazer o que acabou de fazer. Sendo assim você pode utilizar o rollback (use com moderação).
Para desfazer a última migration execute o comando rollback -e development.
01 | juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx |
02 | /bin/phinx rollback -e development |
05 | using config file ./phinx.yml |
06 | using config parser yaml |
08 | - /home/juciellen/Documentos/projetos/expressive/db/migrations |
10 | using environment development |
13 | ordering by creation time |
15 | == 20171124233848 MinhaPrimeiraMigration: reverting |
16 | == 20171124233848 MinhaPrimeiraMigration: reverted 0.0429s |
O bacana de escrever a migration no método change utilizando os métodos específicos do phinx (table, addColumn, etc) é que em caso de rollback o phinx saberá exatamente como desfazer a ação. Se usarmos o método up ao invés do change, em caso de rollback se precisarmos desfazer o que foi definido no up vamos precisar escrever as instruções para isso no método down.
Após o rollback, meu banco não conterá mais a tabela users criada anteriormente.
3 | Schema | Name | Type | Owner |
5 | public | phinxlog | table | jucabrera |
Como o change é invocado também no rollback, não o utilize em migrations de inserções de dados por exemplo, apenas para migrations de alteração de estrutura de tabelas.
Caso você precise dar rollback em várias migrations passe o parametro -t CÓDIGO_DA_MIGRATION_LIMITE
Utilizando SQL
Você também tem a opção de escrever uma instrução em SQL utilizando o método execute para casos em você não queira (ou não possa) utilizar os métodos onde toda a lógica de execução de comandos no banco de dados está abstraída. Isso ocorre por exemplo quando você quer executar comandos relacionados a dados e não à estrutura (INSERT, UPDATE, DELETE, SELECT).
3 | $this ->execute("INSERT INTO users (name, email, password) VALUES |
4 | ( 'teste' , 'teste@teste.com' , 'senha_segura' )"); |
Notem que utilizei o método up e não o change. Conforme mencionei acima, se você escrever no change e der rollback, essa instrução será executada novamente duplicando os dados.
Alertas
- Cuidado com os comandos que você escreve e o ambiente em que está executando a migration. Há chances de você executar DELETE ou UPDATE sem WHERE em produção. Se você não quer correr esse risco, remova o bloco production do seu phinx.yml.
- Se você estiver utilizando Git (espero que sim) não esqueça de colocar o phinx.yml no .gitignore senão já sabe né? Seus dados de acesso ao banco de dados estarão expostos.
- Use nomes intuitivos para suas migrations, para que pelo nome você rapidamente consiga localizar o que você precisa.
Conclusão
Não disse que o phinx era simples? Você não precisa ter um conhecimento amplo dessa biblioteca para conseguir utilizá-la.
Com isso o seu processo de atualização do banco de dados nos outros ambientes vai ficar muito mais simples.
Além disso outro fator super importante é conseguir utilizá-lo no seu processo de integração contínua (ou Continuous Integration – CI).
Para saber mais consulte a documentação do phinx nos links:
http://docs.phinx.org/en/latest/
https://book.cakephp.org/3.0/en/phinx.html