Como automatizar alterações no banco de dados com Phinx e DevOps

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:

01juciellen@cabrera:~/Documentos/projetos/expressive$ php composer.phar require robmorgan/phinx
02Using version ^0.9.1 for robmorgan/phinx
03./composer.json has been updated
04Loading composer repositories with package information
05Updating dependencies (including require-dev)
06Package 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%)
10Writing lock file
11Generating 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:

1juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/bin/phinx init
2Phinx by CakePHP - https://phinx.org. 0.8.1
3 
4created ./phinx.yml

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.

01paths:
02migrations: %%PHINX_CONFIG_DIR%%/db/migrations
03 
04environments:
05default_migration_table: phinxlog
06default_database: development
07 
08development:
09adapter: pgsql
10host: localhost
11name: db_test
12user: SEU_USUARIO
13pass: SENHA_SEGURA
14port: 5432
15charset: utf8
16 
17version_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

1juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx create MinhaPrimeiraMigration

O padrão de nome de migration é CamelCase. O resultado é o seguinte:

01Phinx by CakePHP - https://phinx.org. 0.8.1
02 
03using config file ./phinx.yml
04using config parser yaml
05using migration paths
06- /home/juciellen/Documentos/projetos/expressive/db/migrations
07using seed paths
08using migration base class Phinx\Migration\AbstractMigration
09using default template
10created 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.

01<?php
02use Phinx\Migration\AbstractMigration;
03 
04class MinhaPrimeiraMigration extends AbstractMigration
05{
06  /**
07  * Change Method.
08  *
09  * Write your reversible migrations using this method.
10  *
11  * More information on writing migrations is available here:
12  * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
13  *
14  * The following commands can be used in this method and Phinx will
15  * automatically reverse them when rolling back:
16  *
17  * createTable
18  * renameTable
19  * addColumn
20  * renameColumn
21  * addIndex
22  * addForeignKey
23  *
24  * Remember to call "create()" or "update()" and NOT "save()" when working
25  * with the Table class.
26  */
27  public function change()
28  {
29 
30  }
31}

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.

01public function change()
02    {
03        $this->table('users')
04            ->addColumn('name', 'string')
05            ->addColumn('email', 'string', [
06            'null' => false
07        ])
08            ->addColumn('password', 'string', [
09            'null' => false
10        ])
11            ->addColumn('active', 'boolean', [
12            'null' => false,
13            'default' => true
14        ])
15            ->addColumn('created', 'datetime',['default'=>'CURRENT_TIMESTAMP'])
16            ->create();      
17    }
18}

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.

01juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx migrate -e development
02Phinx by CakePHP - https://phinx.org. 0.8.1
03 
04using config file ./phinx.yml
05using config parser yaml
06using migration paths
07- /home/juciellen/Documentos/projetos/expressive/db/migrations
08using seed paths
09using environment development
10using adapter pgsql
11using database db_test
12 
13== 20171124233848 MinhaPrimeiraMigration: migrating
14== 20171124233848 MinhaPrimeiraMigration: migrated 0.1273s
15 
16All Done. Took 0.1428s

Após a execução a tabela foi criada, bem como a respectiva sequence. Simples não?

 

01db_test=> \d
02List of relations
03Schema | Name | Type | Owner
04--------+--------------+----------+-----------
05public | phinxlog | table | jucabrera
06public | users | table | jucabrera
07public | users_id_seq | sequence | jucabrera
08(3 rows)
09 
10db_test=> \d users
11Table "public.users"
12Column | Type | Modifiers
13----------+-----------------------------+----------------------------------------------------
14id | integer | not null default nextval('users_id_seq'::regclass)
15name | character varying(255) | not null
16email | character varying(255) | not null
17password | character varying(255) | not null
18active | boolean | not null default true
19created | timestamp without time zone | not null default now()
20Indexes:
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.

01juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx status -e development
02Phinx by CakePHP - https://phinx.org. 0.8.1
03 
04using config file ./phinx.yml
05using config parser yaml
06using migration paths
07- /home/juciellen/Documentos/projetos/expressive/db/migrations
08using seed paths
09using environment development
10ordering by creation time
11 
12Status [Migration ID] Started Finished Migration Name
13----------------------------------------------------------------------------------
14up 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.

01juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx
02/bin/phinx rollback -e development
03Phinx by CakePHP - https://phinx.org. 0.8.1
04 
05using config file ./phinx.yml
06using config parser yaml
07using migration paths
08- /home/juciellen/Documentos/projetos/expressive/db/migrations
09using seed paths
10using environment development
11using adapter pgsql
12using database db_test
13ordering by creation time
14 
15== 20171124233848 MinhaPrimeiraMigration: reverting
16== 20171124233848 MinhaPrimeiraMigration: reverted 0.0429s
17 
18All Done. Took 0.0577s

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.

1db_test=> \d
2List of relations
3Schema | Name | Type | Owner
4--------+----------+-------+-----------
5public | 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).

1public function up()
2{
3    $this->execute("INSERT INTO users (name, email, password) VALUES
4     ('teste','teste@teste.com','senha_segura')");
5}

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

 

 

 

 

 

Anterior Descubra o Apache Guacamole: a solução para acessos remotos na sua empresa
Próxima Cresce demanda por especialistas em banco de dados noSQL: 4Linux lança curso de MongoDB

About author

Juciellen Cabrera
Juciellen Cabrera 5 posts

Programadora e Instrutora de PHP em 4Linux | Rankdone. Membro das comunidades PHPSP e PHPWomen. Uma das poucas mulheres com certificação ZCPE no Brasil.

View all posts by this author →

Você pode gostar também

Infraestrutura TI

Sopa de Letrinhas: Decifrando os “Ops” do Mundo DevOps

Se você já se aventurou pelo universo DevOps, provavelmente percebeu que existe uma verdadeira sopa de letrinhas que acompanha o termo. São tantos “Ops” que parece até um feitiço de

Containers

Soft Skills: As Habilidades Essenciais para o Sucesso na Carreira de TI

Soft Skills: As Habilidades Essenciais para o Sucesso na Carreira de TI Em um mercado de tecnologia em constante evolução, as Soft Skills, ou habilidades interpessoais, se tornaram ferramentas indispensáveis

DevOps

Melhore a segurança do seu ambiente Kubernetes com práticas eficazes

A ampla utilização do Kubernetes (K8S) em ambientes produtivos traz uma alerta, de como esses ambientes estão sendo usados em relação as configurações e boas práticas de segurança da informação.