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:

juciellen@cabrera:~/Documentos/projetos/expressive$ php composer.phar require robmorgan/phinx
Using version ^0.9.1 for robmorgan/phinx
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
- Installing symfony/filesystem (v3.3.13): Downloading (100%)
- Installing symfony/config (v3.3.13): Downloading (100%)
- Installing robmorgan/phinx (v0.9.1): Downloading (100%)
Writing lock file
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:

juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/bin/phinx init
Phinx by CakePHP - https://phinx.org. 0.8.1

created ./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.

paths:
migrations: %%PHINX_CONFIG_DIR%%/db/migrations

environments:
default_migration_table: phinxlog
default_database: development

development:
adapter: pgsql
host: localhost
name: db_test
user: SEU_USUARIO
pass: SENHA_SEGURA
port: 5432
charset: utf8

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

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:

Phinx by CakePHP - https://phinx.org. 0.8.1

using config file ./phinx.yml
using config parser yaml
using migration paths
- /home/juciellen/Documentos/projetos/expressive/db/migrations
using seed paths
using migration base class Phinx\Migration\AbstractMigration
using default template
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.

<?php
use Phinx\Migration\AbstractMigration;

class MinhaPrimeiraMigration extends AbstractMigration
{
  /**
  * Change Method.
  *
  * Write your reversible migrations using this method.
  *
  * More information on writing migrations is available here:
  * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
  *
  * The following commands can be used in this method and Phinx will
  * automatically reverse them when rolling back:
  *
  * createTable
  * renameTable
  * addColumn
  * renameColumn
  * addIndex
  * addForeignKey
  *
  * Remember to call "create()" or "update()" and NOT "save()" when working
  * with the Table class.
  */
  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.

public function change()
    {
        $this->table('users')
            ->addColumn('name', 'string')
            ->addColumn('email', 'string', [
            'null' => false
        ])
            ->addColumn('password', 'string', [
            'null' => false
        ])
            ->addColumn('active', 'boolean', [
            'null' => false,
            'default' => true
        ])
            ->addColumn('created', 'datetime',['default'=>'CURRENT_TIMESTAMP'])
            ->create();       
    }
}

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.

juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx migrate -e development
Phinx by CakePHP - https://phinx.org. 0.8.1

using config file ./phinx.yml
using config parser yaml
using migration paths
- /home/juciellen/Documentos/projetos/expressive/db/migrations
using seed paths
using environment development
using adapter pgsql
using database db_test

== 20171124233848 MinhaPrimeiraMigration: migrating
== 20171124233848 MinhaPrimeiraMigration: migrated 0.1273s

All Done. Took 0.1428s

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

 

db_test=> \d
List of relations
Schema | Name | Type | Owner
--------+--------------+----------+-----------
public | phinxlog | table | jucabrera
public | users | table | jucabrera
public | users_id_seq | sequence | jucabrera
(3 rows)

db_test=> \d users
Table "public.users"
Column | Type | Modifiers
----------+-----------------------------+----------------------------------------------------
id | integer | not null default nextval('users_id_seq'::regclass)
name | character varying(255) | not null
email | character varying(255) | not null
password | character varying(255) | not null
active | boolean | not null default true
created | timestamp without time zone | not null default now()
Indexes:
"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.

juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx/bin/phinx status -e development
Phinx by CakePHP - https://phinx.org. 0.8.1

using config file ./phinx.yml
using config parser yaml
using migration paths
- /home/juciellen/Documentos/projetos/expressive/db/migrations
using seed paths
using environment development
ordering by creation time

Status [Migration ID] Started Finished Migration Name
----------------------------------------------------------------------------------
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.

juciellen@cabrera:~/Documentos/projetos/expressive$ php vendor/robmorgan/phinx
/bin/phinx rollback -e development
Phinx by CakePHP - https://phinx.org. 0.8.1

using config file ./phinx.yml
using config parser yaml
using migration paths
- /home/juciellen/Documentos/projetos/expressive/db/migrations
using seed paths
using environment development
using adapter pgsql
using database db_test
ordering by creation time

== 20171124233848 MinhaPrimeiraMigration: reverting
== 20171124233848 MinhaPrimeiraMigration: reverted 0.0429s

All 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.

db_test=> \d
List of relations
Schema | Name | Type | Owner
--------+----------+-------+-----------
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).

public function up()
{
    $this->execute("INSERT INTO users (name, email, password) VALUES 
     ('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

 

 

 

 

 

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

Desenvolvimento

Descubra o Redis: a solução open source para armazenamento de dados

Administradores de sistemas e desenvolvedores provavelmente em algum momento já precisaram de alguma solução para armazenar dados temporários – como token de sessão – que sejam acessíveis de um ponto

Notícias

Alavanque seu negócio com a cultura DevOps: saiba como implementar

Estamos caminhando para fim de 2021 e muitas empresas ainda precisam se recuperar dos efeitos da pandemia e alavancar. Enquanto isso, outras se mantêm fortes e em crescimento, como as

DevOps

Como configurar um repositório Yum local com Nexus Sonatype

Administradores de sistemas, sempre que operam em ambiente GNU/Linux,  realizam a instalação de pacotes pré-compilados localizados em repositórios remotos. Para distribuições baseadas em Red Hat, fazemos uso de repositórios Yum