Doctrine Migrations com Zend Framework

Post publicado em 22/01/2018 08:15 Última atualização em 04/08/2019 20:38

[vc_row][vc_column][vc_column_text]Em 2014 eu escrevi um artigo sobre a ferramenta de migração de banco de dados (migrations) para o CakePHP 2. Hoje, trabalho majoritariamente com o Zend Framework em suas versões 2 e 3, e sempre acabo utilizando migrations em meus projetos. Migrations é uma maneira que possuímos, na camada de programação, de manter o banco de dados estruturalmente sincronizado. Ou seja, uma ferramenta para que não precisemos ficar passando dump da versão atual do banco para o colega de trabalho. Por cuidarem somente da estrutura do banco de dados (DDL - Data Definition Language ), comumente as ferramentas de migração não são backup.

Aplicação

As ferramentas de migrations nos permitem evoluir gradativamente nosso banco de dados e, estando no sistema de versionamento (ex: git), permite que todos os envolvidos possam ter a estrutura da base de dados sincronizada. Vamos ilustrar a aplicação com 2 programadores trabalhando remotamente em um sistema de locação de equipamentos médicos. Daremos os nomes de Marcos e José para estes programadores fictícios. Devido à uma necessidade no status do equipamento, José cria um campo no banco de dados com a intenção de utilizar como uma flag para reserva com ressalva. Marcos dá um pull no projeto e recebe todos os arquivos adicionados e editados por José. Assim que vai testar a feature que estava desenvolvendo, também referente à equipamentos, uma excessão é lançada. Nos detalhes ele identifica que está faltando um campo chamado "ressalva" (é, estão nomeando em pt_BR, afinal de contas, é somente para esta didática). A primeira pergunta que vem à cabeça do Marcos é: WTH é esse campo? Pra que serve? Qual a definição? É uma string, int...? Numa situação sem migrations, Marcos entraria em contato com José para entender o que tem que fazer pra corrigir o erro e aguardaria a resposta. Por estarem trabalhando com ZF3 e o ORM Doctrine , utilizaram desde o começo o Doctrine Migrations para trabalhar em conjunto. Ao perceber o erro informando que falta um campo na tabela equipamentos, Marcos instantaneamente sabe que precisa rodar o migrations. Executando um simples comando a estrutura do seu banco fica semelhante a de José e o erro anterior não mais existe. Este foi apenas um cenário hipotético, mas muito comum no dia a dia de desenvolvedores trabalhando em equipe. A partir de agora você conhecerá mais detalhes de como o Marcos e o José trabalham com migrations. [quads id=1]

Como ler este artigo

Dividi em 3 partes para que você consiga realizar uma pausa para tomar um café, ou mesmo descansar um pouco, visto que é bem extenso. No intervalo entre cada uma das partes dou um break e sugiro a leitura de outros conteúdos e livros. Na primeira parte será criado um projeto com o Zend Framework 3, criado o banco de dados instalado o ORM Doctrine e realizadas as devidas configurações. Na segunda será instalado o Doctrine Migrations, explicados os comandos e inicializadas as migrações na aplicação. E por fim a terceira parte será onde as migrações serão colocadas em prática.

Requerimentos

  • PHP 5.6 ou superior
  • Mysql 5.5 ou superior
É solicitado o Mysql porque o migrations somente funciona com o este banco de dados.

Iniciando

Inicie um projeto do Zend Skeleton Application . Para simplificar todo nosso exemplo, o skeleton application será utilizado por já nos fornecer uma aplicação básica com o ZF3. Utilizaremos o composer para esta instalação então, se você não sabe o que é, sugiro esta leitura primeiro.
composer create-project -s dev zendframework/skeleton-application path/to/install

O que este comando faz?

  • Cria uma pasta com o nome informado
  • Instala o ZF3 com suas dependências
  • Questiona se você deseja algumas configurações
  • Questiona se quer remover o versionamento original do projeto (.git, .svn...)
  • Habilita o modo desenvolvimento

Estrutura de pastas

Esta é uma estrutura mínima possível com o Zend Skeleton Application. Perceba que existe uma pasta chamada module e nela um módulo apenas chamado Application. Não criaremos outro módulo neste exemplo, faremos tudo dentro do Application mesmo.

Ok, mas o que são módulos?

De forma resumida, módulos são pequenas aplicações, resolvendo um problema específico. A intenção é que estes módulos sejam independentes e auto-configuráveis. Desta forma um módulo da aplicação X pode ser utilizado na aplicação Y com poucas, ou preferencialmente, nenhuma configuração adicional.

Finalizando a instalação

Finalizada a instalação, entre na pasta que informou para a criação e rode o comando composer. Note que existe uma opção chamada serve. Use-a.
composer serve
O resultado é como da imagem a seguir. Agora acesse o endereço descrito em seu navegador. Ah, também funciona com https://www.andrebian.com ;) Observação: Tive um erro ao rodar desta forma e talvez ele ocorra pra você também. No arquivo composer.json que veio com o Zend Skeleton o script serve possuía a seguinte definição:
"serve": "php -S 0.0.0.0:8080 -t public public/index.php"
Ao rodar pelo composer serve o seguinte erro era exibido: Erro ao rodar o composer serve Após uma pequena correção no script, tudo funcionou. Veja o antes e depois.
# Antes
"serve": "php -S 0.0.0.0:8080 -t public public/index.php"

# Depois
"serve": "php -S 0.0.0.0:8080 -t public"
A partir deste ponto pode parar o server e fechar a aba referente a aplicação recém criada. Todos os exemplos que virão serão exclusivamente via linha de comando. Apenas lhe mostrei a aplicação rodando para que tenha certeza de que está tudo certo para prosseguirmos.

Banco de dados

Crie um banco de dados qualquer no seu mysql. Por questão de exemplo, criarei um chamado zf3_blog.
mysql -u root -p
CREATE SCHEMA zf3_blog CHARACTER SET utf8 COLLATE utf8_unicode_ci;
 

Adicionando o Doctrine

Para o Zend Framework existe um módulo do Doctrine, lembra-se que expliquei que no ZF trabalha-se com módulos, né?! Rode o seguinte comando:
composer require doctrine/doctrine-orm-module
Durante a instalação você será questionado duas vezes se deseja incluir o módulo automaticamente no arquivo de configurações dos módulos. Digite 1 para adicionar no arquivo correto (neste cenário que estamos contruindo com o Skeleton Application). A primeira vez refere-se ao Zend Hydrator, a segunda sobre o DoctrineModule. Ao final o seu arquivo config/module.config.php deve conter o conteúdo semelhante à este:
<?php
/**
 * @link http://github.com/zendframework/ZendSkeletonApplication for the canonical source repository
 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
 * @license http://framework.zend.com/license/new-bsd New BSD License
 */

/**
 * List of enabled modules for this application.
 *
 * This should be an array of module namespaces used in the application.
 */
return [
    'ZendCache',
    'ZendForm',
    'ZendInputFilter',
    'ZendFilter',
    'ZendPaginator',
    'ZendHydrator',
    'ZendRouter',
    'ZendValidator',
    'DoctrineModule',
    'DoctrineORMModule',
    'Application',
];
Caso não possua os valores 'ZendHydrator', 'DoctrineModule' e 'DoctrineORMModule', os adicione manualmente. Eles são necessários para nossa aplicação de exemplo. Após finalizada a instalação, verifique se o DoctrineModule está presente e funcionando corretamente.
./vendor/bin/doctrine-module
Se tudo ocorreu bem, é para o comando acima gerar uma saída semelhante à esta:

Conectando o banco de dados na aplicação

Até agora já temos nosso banco de dados, o DoctrineModule instalado e rodando, mas falta ainda a conexão. Rode o comando abaixo para verificar que um erro é lançado por não existir ainda configuração de conexão.
./vendor/bin/doctrine-module orm:validate-schema
Para corrigir, na pasta config/autoload, crie um arquivo chamado doctrine_orm.local.php. Neste arquivo defina suas configurações do banco de dados, semelhante ao código a seguir.
<?php
# config/autoload/doctrine_orm.local.php

return [
    'doctrine' => [
        'connection' => [
            'orm_default' => [
                'driverClass' => 'DoctrineDBALDriverPDOMySqlDriver',
                'params' => [
                    'host' => 'localhost',
                    'port' => '3306',
                    'user' => 'root',
                    'password' => 'root',
                    'dbname' => 'zf3_blog',
                    'driverOptions' => [
                        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"
                    ]
                ]
            ]
        ]
    ],
];
Rodando novamente o comando anterior, temos o problema da conexão resolvido.

Concluindo a primeira parte

Até o momento foi criado um projeto com base no Zend Skeleton Application, adicionado o DoctrineModule e o configurado. Ao final desta primeira parte, foi possível ver que a conexão com o banco de dados está ok. Também, como exibido na imagem anterior, que nosso banco de dados está em conformidade com o mapeamento. Isto porque ainda não temos nenhuma entidade e o banco está em branco. Na segunda parte, iniciaremos o trabalho de fato com as migrações.   [/vc_column_text][us_cta title="Quer saber mais sobre o ORM Doctrine?" color="light" btn_link="url:https%3A%2F%2Fleanpub.com%2Fdoctrine-na-pratica||target:%20_blank|" btn_label="Ver o livro" btn_style="3" btn_size="20px"]Sugiro a leitura do livro do Elton Minetto[/us_cta][vc_column_text]

Instalando o Doctrine Migrations

Rode o comando a seguir.
composer require doctrine/migrations
Diferente do DoctrineModule e DoctrineORMModule, o Doctrine Migrations não precisa ser registrado nos módulos da aplicação. Após a instalação, execute o comando abaixo, e se está no mesmo estágio que eu, é para dar um erro.
./vendor/bin/doctrine-module
O erro diz que a pasta necessária para gerar as migrações não existe, então, crie-a.
mkdir -p data/DoctrineORMModule/Migrations
Em seguida rode o comando ./vendor/bin/doctrine-module novamente. Desta vez vai aparecer em meio às demais opções, as funcionalidades de migração.

Inicializando as migrações

Agora já temos tudo pronto para trabalhar com as migrações. Conforme visto na imagem anterior, existem diversas opções para a lib doctrine migrations, qual delas utilizar? Em dados momentos, você pode utilizadas todas elas. Mas explicarei num passo a passo algumas delas, as principais. O primeiro passo é inicializar as migrações. Isto fará com que seja criado um arquivo de inicialização que não contém nenhuma operação dentro up e down.
./vendor/bin/doctrine-module migrations:generate
[caption id="attachment_6492" align="alignnone" width="995"] Resultado da inicialização das migrações[/caption]   [caption id="attachment_6493" align="alignnone" width="828"]Primeiro arquivo de migrações Primeiro arquivo de migrações[/caption]   Já está pronto? Não! Até o dado momento nosso banco de dados não possui nenhuma tabela; [caption id="attachment_6494" align="alignnone" width="331"]Banco de dados ainda sem tabelas Banco de dados ainda sem tabelas[/caption]   Agora vamos utilizar nosso segundo comando, o migrate.
./vendor/bin/doctrine-module migrations:migrate
Ao ser questionado(a) se tem certeza que quer prosseguir, confirme. Note o aviso que é exibido, informando que dados podem ser perdidos. Por este motivo que em produção este comando deve ser utilizado com muita cautela. [caption id="attachment_6495" align="alignnone" width="1253"]Migrations sendo propagada no banco de dados Migrations sendo propagada no banco de dados[/caption]   Agora o banco de dados já possui uma tabela chamada migrations para que seja adicionada cada uma das migrações executadas. [caption id="attachment_6496" align="alignnone" width="571"]Verficando o banco de dados Verficando o banco de dados[/caption]   Note que os comandos da imagem anterior formam um passo a passo de algumas verificações. Verificam-se as tabelas, em seguida a estrutura e por último os dados. Recapitulando: Na primeira parte foi instalado o Zend Skeleton, o DoctrineModule, criado o banco de dados e configurada a conexão. Nesta segunda parte, foi instalado o Doctrine Migrations, realizados pequenos ajustes e inicializado migrations em nossa aplicação. Na terceira parte, veremos como é o funcionamento das migrações no dia a dia.

Nossa primeira entidade

No Doctrine criamos entidades que são classes que representam uma tabela no banco de dados implementando o design pattern Data Mapper . Dentro de module/Application/src crie uma pasta chamada Entity e dentro dela um arquivo chamado Post.php. O conteúdo será descrito em partes para o completo entendimento.

Imports

Defina primeiramente o namespace da classe Post. Em seguida importe a classe DateTime e o namespace DoctrineORMMapping, que contém diversas classes que utilizaremos a seguir.
<?php
# module/Application/src/Entity/Post.php

namespace ApplicationEntity;

use DateTime;
use DoctrineORMMapping as ORM;
Foi dado o apelido de ORM para simplificar a utilização posterior.

Mapeamento

Existem diversas formas de se mapear entidades com o Doctrine: xml , yaml , php e por docblock annotations , que é que mais utilizo por causa da simplicidade. No docblock da classe informe que a mesma trata-se de uma tabela (@ORMTable) e também é um objeto a ser entendido como entidade (@ORMEntity), como no trecho de código abaixo.
/**
 * Class Post
 * @package ApplicationEntity
 *
 * @ORMTable(name="posts")
 * @ORMEntity()
 */
class Post

Propriedades da classe

O fato de trabalhar com docblock annotations nos permite criar uma classe totalmente em PHP para definir como será a estrutura de uma tabela no banco de dados. Um dos recursos mais legais de se trabalhar com ORM é a possibilidade de não precisar pensar no banco de dados como um todo. Você vai programando e conforme vai surgindo a necessidade de novas tabelas, cria uma entidade que posteriormente é espelhada com o banco de dados. Para o nosso exemplo, teremos somente um título, o conteúdo e as datas de criação e alteração.
/**
 * @ORMId
 * @ORMColumn(type="integer")
 * @ORMGeneratedValue(strategy="IDENTITY")
 * @var int
 */
private $id;

/**
 * @var string
 * @ORMColumn(type="string", length=255, nullable=false)
 */
private $title;

/**
 * @var string
 * @ORMColumn(type="text", nullable=false)
 */
private $content;

/**
 * @var DateTime
 * @ORMColumn(type="datetime", nullable=false)
 */
private $created;

/**
 * @var DateTime
 * @ORMColumn(type="datetime", nullable=false)
 */
private $modified;
Perceba como é simples a definição das anotações, os tipos do Doctrine Annotations são muito semelhantes aos do PHP puro.

Getters and Setters

Como manipularemos esta entidade toda vez que uma consulta na tabela que a mesma representa e as propriedades estão com visibilidade privada, precisamos de nossos getters e setters. Lembra que lá no início, na primeira parte, foi adicionado uma lib chamada Hydrator? Então, graças aos getters e setters é possível popular uma entidade de forma simplificada através de um simples array, sem a necessidade de ficar setando um a um (setTitle(), setContent()...).
/**
 * @return int
 */
public function getId()
{
    return $this->id;
}

/**
 * @param int $id
 * @return Post
 */
public function setId($id)
{
    $this->id = $id;
    return $this;
}

/**
 * @return string
 */
public function getTitle()
{
    return $this->title;
}

/**
 * @param string $title
 * @return Post
 */
public function setTitle($title)
{
    $this->title = $title;
    return $this;
}

/**
 * @return string
 */
public function getContent()
{
    return $this->content;
}

/**
 * @param string $content
 * @return Post
 */
public function setContent($content)
{
    $this->content = $content;
    return $this;
}

/**
 * @return DateTime
 */
public function getCreated()
{
    return $this->created;
}

/**
 * @param DateTime $created
 * @return Post
 */
public function setCreated($created)
{
    $this->created = $created;
    return $this;
}

/**
 * @return DateTime
 */
public function getModified()
{
    return $this->modified;
}

/**
 * @param DateTime $modified
 * @return Post
 */
public function setModified($modified)
{
    $this->modified = $modified;
    return $this;
}

Hydrator

Para a lib Hydrator daremos duas utilidades: 1) extrair os dados da entidade para um array e 2) popular a entidade com base em um array recebido pelo construtor.
# nos imports
use ZendHydratorClassMethods;
/**
 * Extrai a entidade para um array
 * @return array
 */
public function toArray()
{
    return (new ClassMethods(false))->extract($this);
}
/**
 * Recebe um array e popula a entidade
 * @param array $data
 */
public function __construct($data = [])
{
    $this->created = new DateTime();
    $this->modified = new DateTime();

    if (!empty($data)) {
        (new ClassMethods(false))->hydrate($data, $this);
    }
}
O parâmetro false ao instanciar a classe ClassMethods é necessário porque a lib possui retrocompatibilidade com o ZF1, onde os namespaces eram definidos com underscores ( _ ). Como no ZF2 e ZF3 os namespaces seguem a PSR-0 e PSR4, faz-se necessário indicar que não é pra utilizar o underscore como separador de namespace do ZF1. Um exemplo de população através do Hydrator é o seguinte:
$post = new Post([
    'title' => 'Post Teste',
    'content' => 'teste de conteudo'
]);

// output
print_r($post);
ApplicationEntityPost Object
(
    [id:ApplicationEntityPost:private] => 
    [title:ApplicationEntityPost:private] => Teste
    [content:ApplicationEntityPost:private] => teste de onteúdo
    [created:ApplicationEntityPost:private] => DateTime Object
    (
        [date] => 2018-01-18 02:48:13.088643
        [timezone_type] => 3
        [timezone] => America/Sao_Paulo
    )
    [modified:ApplicationEntityPost:private] => DateTime Object
    (
        [date] => 2018-01-18 02:48:13.088655
        [timezone_type] => 3
        [timezone] => America/Sao_Paulo
    )
)

Resultado

E aqui está nossa primeira entidade na íntegra.
<?php
# module/Application/src/Entity/Post.php

namespace ApplicationEntity;

use DateTime;
use DoctrineORMMapping as ORM;
use ZendHydratorClassMethods;

/**
 * Class Post
 * @package ApplicationEntity
 *
 * @ORMTable(name="posts")
 * @ORMEntity()
 */
class Post
{
    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue(strategy="IDENTITY")
     * @var int
     */
    private $id;

    /**
     * @var string
     * @ORMColumn(type="string", length=255, nullable=false)
     */
    private $title;

    /**
     * @var string
     * @ORMColumn(type="text", nullable=false)
     */
    private $content;

    /**
     * @var DateTime
     * @ORMColumn(type="datetime", nullable=false)
     */
    private $created;

    /**
     * @var DateTime
     * @ORMColumn(type="datetime", nullable=false)
     */
    private $modified;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param int $id
     * @return Post
     */
    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $title
     * @return Post
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @param string $content
     * @return Post
     */
    public function setContent($content)
    {
        $this->content = $content;
        return $this;
    }

    /**
     * @return DateTime
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * @param DateTime $created
     * @return Post
     */
    public function setCreated($created)
    {
        $this->created = $created;
        return $this;
    }

    /**
     * @return DateTime
     */
    public function getModified()
    {
        return $this->modified;
    }

    /**
     * @param DateTime $modified
     * @return Post
     */
    public function setModified($modified)
    {
        $this->modified = $modified;
        return $this;
    }

    /**
     * Extrai a entidade para um array
     * @return array
     */
    public function toArray()
    {
        return (new ClassMethods(false))->extract($this);
    }

    /**
     * Recebe um array e popula a entidade
     * @param array $data
     */
    public function __construct($data = [])
    {
        $this->created = new DateTime();
        $this->modified = new DateTime();

        if (!empty($data)) {
            (new ClassMethods(false))->hydrate($data, $this);
        }
    }
}
[quads id=1]   Agora vamos ao que interessa. Já temos tudo configurado para as migrações: inicializamos e no momento temos uma entidade que está mapeada mas não sincronizada com o banco de dados. Para verificar o sincronismo com o banco rodamos o comando ./vendor/bin/doctrine-module orm:v (preguiça né... o correto é orm:validate-schema, mas digitando apenas orm:v e dando enter já funciona). Validando a estrutura Poxa... diz que está ok, mas o que aconteceu? Afinal de contas já criamos uma entidade, deveria ter acusado que o banco de dados não está em sincronia. Isso ocorreu porque o nossa aplicação, que foi construída com o ZF3, ainda não tem conhecimento de onde estão as entidades. No arquivo module/Application/config/module.config.php adicione o seguinte trecho de código.
# nos imports
use DoctrineORMMappingDriverAnnotationDriver;

...
'doctrine' => [
    'driver' => [
        __NAMESPACE__ . '_driver' => [
            'class' => AnnotationDriver::class,
            'cache' => 'array', // apc...
            'paths' => [dirname(__DIR__) . '/src/Entity']
        ],
        'orm_default' => [
            'drivers' => [
                __NAMESPACE__ . 'Entity' => __NAMESPACE__ . '_driver'
            ]
        ]
    ],
],
Agora validando novamente nossa estrutura, temos a informação de que o mapeamento está correto (as entidades) mas o banco de dados não está sincronizado. Banco não sincronizado Pronto, agora sim, estamos plenamente aptos a realizar nossa primeira migração no mundo real!   [/vc_column_text][us_cta title="Interesse em TDD?" title_size="h3" color="custom" bg_color="#422b72" text_color="#ffffff" btn_link="url:https%3A%2F%2Ftddcomphp.com.br||target:%20_blank|" btn_label="Conheça meu livro" btn_size="20px" btn_style="4"]
Então meu livro é perfeito pra você!
Pra ficar melhor ainda, tenho um cupom de 15% de desconto. Clique em "Conheça meu livro" e pegue o seu![/us_cta][vc_column_text]

 

Primeira migração real

O comando do Doctrine Migrations a ser utilizado desta vez é o diff. Este comando compara a estrutura do banco de dados com o mapeamento das entidades.
./vendor/bin/doctrine-module migrations:diff
primeira migracao Agora na pasta data/DoctrineORMModule/Migrations existe mais um arquivo de migração e o seu conteúdo: conteudo da migração Vamos aos detalhes. Existem dois métodos, up e down, que servem para realizar uma migração e retornar ao estágio anterior em caso de rollback da migração. Outro ponto que você deve ter reparado é que a primeira coisa que é feita em cada um dos métodos é a verificação do banco de dados. Caso não seja o Mysql a migração é abortada. Tudo pronto? Nem tudo pronto ainda Não!!! O que fizemos até então foi apenas comparar o nosso mapeamento com a situação atual do banco de dados, gerando o resultado desta diferença em um arquivo que poderá ser utilizado posteriormente.

Enfim, Migrations!

Chegou a hora, a primeira migração no mundo real. Rode o comando a seguir. No momento que solicitar a confirmação, digite y e pressione enter.
./vendor/bin/doctrine-module migrations:migrate
Rodando a migração O que aconteceu aqui é que o arquivo recém gerado pelo comando diff foi lido e executado o seu método up, fazendo assim alterações serem realizadas no banco de dados. Caso existissem diversos arquivos ainda não sincronizados, todos estes seriam processados, surtindo suas alterações no banco. E nosso banco de dados, como ficou? banco de dados após migração Perfeito, agora vou adicionar uma nova propriedade na entidade para criar mais um caso de migração.
/**
 * @var string
 * @ORMColumn(type="string", length=500, nullable=true)
 */
private $featuredImage;

/**
 * @return string
 */
public function getFeaturedImage()
{
    return $this->featuredImage;
}

/**
 * @param string $featuredImage
 * @return Post
 */
public function setFeaturedImage($featuredImage)
{
    $this->featuredImage = $featuredImage;
    return $this;
}
Novamente rodando o comando diff e em seguida o comando migrate o novo campo é adicionado na tabela posts.
./vendor/bin/doctrine-module migrations:diff
./vendor/bin/doctrine-module migrations:migrate --no-interaction
novo campo Note que no momento de rodar a migração foi adicionado um novo parâmetro, o "--no-interaction". Ele foi adicionado para não realizar perguntas, simplesmente executar a migração e pronto! No entanto friso: isso é arriscado, somente utilize desta forma se tiver absoluta certeza do que está fazendo, principalmente em produção.

Revertendo migrações

Como você já viu, o arquivo de migração possui 2 métodos, up e down. O up serve para realizar as alterações no banco de dados, já o down para reverter tais alterações. Você pode selecionar uma versão e reverter a mesma, da seguinte forma:
./vendor/bin/doctrine-module migrations:execute YYYYMMDDHHMMSS --down
Veja o exemplo: revertendo migração Simples não?!

Deixando as migrações mais simples

Pra simplificar você pode utilizar os scripts do composer.json para criar apelidos aos comandos do Doctrine Migrations, veja o exemplo.
# composer.json
"scripts": {
    "diff-db": "doctrine-module migrations:diff",
    "migrate-db": "doctrine-module migrations:migrate --no-interaction"
}
Aí para criar um arquivo de migração, aquele que é criado quando uma nova entidade é adicionada ou algum atributo de uma entidade existente é alterado, roda-se o comando:
composer diff-db
Para atualizar a estrutura do banco de dados em conformidade com os arquivos de migração ainda não sincronizados, roda-se o comando:
composer migrate-db

Conclusão

Desde que comecei a trabalhar com o Doctrine Migrations simplesmente o utilizo em todos os projetos, mesmo nos que trabalho sozinho. Isso porque mesmo trabalhando sozinho, ainda tenho que subir as alterações em homologação e/ou produção. No meu cenário atual, faço deploy com git hooks, que menciono neste artigo, e no post-receive adiciono o comando para que as migrações sejam executadas.
# .git/hooks/post-receive 
cd project/path; ./vendor/bin/doctrine-module migrations:migrate --no-interaction
Assim toda vez que vou colocar as alterações em homologação ou produção, a estrutura do banco de dados é automaticamente sincronizada. Somente envio algum dump do banco quando a alteração mexe com relacionamentos e estes acabam se quebrando. Se você chegou até aqui, caramba, parabéns! Como mencionei no início, este é um artigo extenso, por isso o dividi em partes. Agora pra compensar o tempo que você investiu na leitura, vou lhe dar um presente (não fique bravo(a)): Eu fiz um super screencast com exatamente o mesmo conteúdo deste artigo e disponibilizei no youtube. [/vc_column_text][/vc_column][/vc_row]


Scroll down