Doctrine Migrations
Migrations é uma forma de versionar o seu schema de banco de dados. Com migrations a manipulação do banco de dados como a criação, alteração e remoção de tabelas, colunas, etc, pode ser controlada junto com o versionamento da aplicação.
Com migrations a modelagem do banco de dados é escrita em um ou mais arquivos de migration e a criação é executada através de um comando, tornando muito fácil o compartilhamento dela com outros desenvolvedores. Quando alterações no banco forem necessárias, basta criar um novo arquivo de migration com as modificações, sejam elas a criação de tabelas ou a adição colunas a tabelas já existentes por exemplo, e após executar o comando de migração, o próprio mecanismo da migration irá identificar quais arquivos de migration já foram executados e quais não e irá executá-los de acordo. A própria migrations permite que se volte a uma verão anterior do banco(rollback) com um comando.
Vamos conhecer o básico do Doctrine Migrations que é um dos projetos do Doctrine do qual ja vimos um pouco do ORM. Para instalar a biblioteca usamos o composer
composer require "doctrine/migrations"
Configurando o Migrations
Para utilizar o migrations primeiro devemos fornecer algumas informações para o para o aplicativo de console, fazemos isso criando um arquivo de configuração na raiz do projeto, que pode ser no formato PHP, JSON, XML ou yml. Veja o exemplo em PHP:
//migrations.php
<?php
return [
'table_storage' => [
'table_name' => 'doctrine_migration_versions',
'version_column_name' => 'version',
'version_column_length' => 1024,
'executed_at_column_name' => 'executed_at',
'execution_time_column_name' => 'execution_time',
],
'migrations_paths' => [
'App\Migrations' => 'app/Migrations',
],
'all_or_nothing' => true,
'transactional' => true,
'check_database_platform' => true,
];
Vamos ver os detalhes de cada configuração:
- migrations_paths: O namespace onde suas classes de migração estão, é um array onde a chave é o namespace e o valor é o path do diretório.
- table_storage: O nome da tabela que o doctrine migration irá utilizar para rastrear as migrations executadas.
- all_or_nothing: Se deve ou não agrupar várias migration em uma única transação.
- transactional: Se as migration devem ou não ser agrupadas em uma única transação
- migrations: Um array de classes de migration para ser utilizada em vez de localizar por diretório.
- check_database_platform: Se deve adicionar uma verificação de plataforma de banco de dados no início do código gerado.
Dentro da configuração table_storage podemos ter as seguintes configurações
- table_name: O nome da tabela na qual irá rastrear as migrations executadas.
- version_column_name: O nome da coluna que armazena o nome da versão.
- version_column_length: O comprimento da coluna que armazena o nome da versão.
- executed_at_column_name: O nome da coluna que armazena a data em que uma migration foi executada.
- execution_time_column_name: O nome da coluna que armazena quanto tempo uma migration levou (milissegundos).
Agora devemos criar configurar a conexão com banco, a forma mais fácil de fazer isso é criando um arquivo migrations-db.php na raiz do projeto que retorna um array com as configurações de acesso.
<?php
return [
'dbname' => 'migration_test',
'user' => 'root',
'password' => '',
'host' => 'localhost',
'driver' => 'pdo_mysql',
];
Criando as classes de Migration
As classes de migration devem extender Doctrine\Migrations\AbstractMigration e implementar os métodos up e down. A maneira mais fácil de criar uma classe de migration em branco é através do comando:
./vendor/bin/doctrine-migrations generate
Genado assim o seguinte arquivo na pasta especificada nas configurações:
final class Version20220620123119 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
}
}
No método up devemos colocar os códigos de alterações que desejamos fazer em nossa base de dados, por exemplo criar uma nova tabela, e para isso a maneira vamos executar um SQL puro. Para isso usamos o método addSql (calma tem maneira melhor de fazer isso).
public function up(Schema $schema): void
{
$this->addSql( <<<SQL
CREATE TABLE users(
id INT AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY(id)
);
SQL);
}
No método down colocamos os códigos para desfazer as alterações feitas no método up, isso é feito para podermos retornar a versão do banco de dados a uma anterior se necessário. Vejamos o exemplo:
public function down(Schema $schema): void
{
$this->addSql(
<<<SQL
DROP TABLE users;
SQL
);
}
No método getDescription colocamos um descrição textual da versão do banco de dados.
public function getDescription(): string
{
return 'Criação da tabela usuários';
}
Neste ponto podemos verificar o estado de nossa migration utilizando o comando list-migrations
./vendor/bin/doctrine-migrations list-migrations
Para executar a migration no banco utilizamos o comando migrate:
./vendor/bin/doctrine-migrations migrate
Ele irá pedir confirmação da operação pois as alterações podem resultar em perda de dados. Agora é só conferir a tabela criada no banco de dados
Representação de schema
Embora podemos utilizar SQL para nossas migrations, essa não é a melhor prática, e o Doctrine nos fornece uma representação orientada a objetos de um esquema de banco de dados.
Criando Tabela
Para criar uma tabela utilizamos o objeto $schema que recebemos como argumento nos métodos up e down. Dele chamamos o método createTable passando o nome da tabela que queremos criar.
$postsTable = $schema->createTable('posts');
Ele irá retornar um objeto Table do qual podemos chamar o método addColumn($name, $typeName, array $options), onde name é o nome da coluna, typeName é o tipo de dados, e as opções de colunas. Veja um exemplo.
$postsTable->addColumn('id', 'integer', ['autoincrement' => true]);
$postsTable->addColumn('title', 'string', ['length' => 255]);
Veja uma lista dos tipos disponíveis :
+-------------------+---------------+-----------------------------------------------------------------------------------------------+
| Doctrine | PHP | Database vendor |
| | +--------------------------+---------+----------------------------------------------------------+
| | | Name | Version | Type |
+===================+===============+==========================+=========+==========================================================+
| **smallint** | ``integer`` | **MySQL** | *all* | ``SMALLINT`` ``UNSIGNED`` ``AUTO_INCREMENT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``SMALLINT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``NUMBER(5)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` ``AUTO_INCREMENT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``INT`` |
| | | | +----------------------------------------------------------+
| | | | | ``SERIAL`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``NUMBER(10)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` [15] |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **bigint** | ``string`` | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` ``AUTO_INCREMENT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BIGINT`` |
| | | | +----------------------------------------------------------+
| | | | | ``BIGSERIAL`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``NUMBER(20)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **decimal** | ``string`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` |
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+ | |
| | | **SQL Server** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``DOUBLE PRECISION`` |
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+ | |
| | | **SQL Server** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` |
| | +--------------------------+ | |
| | | **PostgreSQL** | | |
| | +--------------------------+ +----------------------------------------------------------+
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``VARCHAR2(n)`` |
| | | | +----------------------------------------------------------+
| | | | | ``CHAR(n)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``NVARCHAR(n)`` |
| | | | +----------------------------------------------------------+
| | | | | ``NCHAR(n)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` |
| | | | | ``CHAR(n)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``TEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``MEDIUMTEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``LONGTEXT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **guid** | ``string`` | **MySQL** | *all* | ``CHAR(36)`` |
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` |
| | +--------------------------+ | |
| | | **PostgreSQL** | *all* | ``UUID`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` |
| | +--------------------------+ | |
| | | **SQL Server** | +----------------------------------------------------------+
| | +--------------------------+ | ``BINARY(n)`` |
| | | **Oracle** | *all* | ``RAW(n)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BYTEA`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``BLOB`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **blob** | ``resource`` | **MySQL** | *all* | ``TINYBLOB`` |
| | | | +----------------------------------------------------------+
| | | | | ``BLOB`` |
| | | | +----------------------------------------------------------+
| | | | | ``MEDIUMBLOB`` |
| | | | +----------------------------------------------------------+
| | | | | ``LONGBLOB`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``BLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARBINARY(MAX)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BYTEA`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BOOLEAN`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``BIT`` |
| | +--------------------------+ | |
| | | **Oracle** | *all* | ``NUMBER(1)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` |
| | +--------------------------+ | |
| | | **PostgreSQL** | | |
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+ |
| | | **SQL Server** | "all" | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **datetime** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``DATETIME`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``TIMESTAMP(0)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **datetimetz** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+ |
| | | **SQL Server** | "all" | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` |
| | +--------------------------+ | |
| | | **Oracle** | | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``DATE`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | "all" | ``TIME(0)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **array** | ``array`` | **MySQL** | *all* | ``TINYTEXT`` |
+-------------------+ | | +----------------------------------------------------------+
| **simple array** | | | | ``TEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``MEDIUMTEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``LONGTEXT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **json** | ``mixed`` | **MySQL** | *all* | ``JSON`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``JSON`` |
| | | | +----------------------------------------------------------+
| | | | | ``JSONB`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **object** | ``object`` | **MySQL** | *all* | ``TINYTEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``TEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``MEDIUMTEXT`` |
| | | | +----------------------------------------------------------+
| | | | | ``LONGTEXT`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARCHAR(MAX)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
Para opções temos os seguintes valores disponíveis
- notnull (boolean): Se a coluna permite valores null ou não. O padrão é true.
- default (integer|string): O valor padrão da coluna se nenhum valor for especificado. O padrão é null.
- autoincrement (boolean): Se a coluna deve ser auto-incrementada se nenhum valor for especificado. Somente é aplicavel aos tipos Doctrine smallint, integer e bigint. O padrão é false.
- length (integer): O tamanho máximo da coluna. Somente se aplica aos tipos string e binary do Doctrine. O padrão é null e é avaliado como 255.
- fixed (boolean): Se um tipo string ou binary do Doctrine tem um tamanho fixo de coluna. O padrão é false.
- precision (integer): A precisão do tipo decimal ou float do Doctrine que determina o número máximo geral de dígitos a serem armazenados(incluindo a escala). O padrão é 10.
- scale (integer): O número exato de digitos decimais a serem armazenado nos tipo decimal e float do Doctrine. O padrão é 0.
- customSchemaOptions (array): Opções adicionais para a coluna que são suportados para todos os vendors:
- unique (boolean): Se deve adicionar automaticamente uma restrição exclusiva para a coluna. O padrão é false.
Para adicionar a chave primária utilizamos o método setPrimaryKey passando um array com as colunas que a compõe.
$postsTable ->setPrimaryKey(["id"]);
Para adicionar um índice utilizamos o método addUniqueIndex passando um array com as colunas que o compõe.
$postsTable->addUniqueIndex(["title"]);
Para adicionar uma chave estrangeira utilizamos o método addForeignKeyConstraint passsando um objeto Table, um array com da chave estrangeira, um array com com a chave primária da tabela destino, e um array de opções.
$commentsTable->addForeignKeyConstraint($postsTable, ['post_id'], ['id'], ['onDelete' => 'CASCADE']);
Veja um exemplo para posts e comentários:
public function up(Schema $schema): void
{
$postsTable = $schema->createTable('posts');
$postsTable->addColumn('id', 'integer', ['autoincrement' => true]);
$postsTable->addColumn('title', 'string', ['length' => 255]);
$postsTable->addColumn('body', 'text');
$postsTable->addColumn('created_at', 'datetime');
$postsTable->setPrimaryKey(['id']);
$commentsTable = $schema->createTable('comments');
$commentsTable->addColumn('post_id', 'integer');
$commentsTable->addColumn('name', 'string');
$commentsTable->addColumn('body', 'text');
$commentsTable->addColumn('created_at', 'datetime');
$commentsTable->addForeignKeyConstraint($postsTable, ['post_id'], ['id'], ['onDelete' => 'CASCADE']);
}
public function down(Schema $schema): void
{
$schema->dropTable('comments');
$schema->dropTable('posts');
}
Gerenciando as Migrations
Como vimos executamos o comando migrate para rodar todas as migrations disponíveis no banco.
./vendor/bin/doctrine-migrations migrate
Para listar todas as migrations e seu estado(executada ou não) executamos o list-migration.
./vendor/bin/doctrine-migrations list-migration
Para reverter uma migration usamos o próprio comando migrate e passamos o nome da migration para a qual queremos voltar.
./vendor/bin/doctrine-migrations migrate "App\Migrations\Version20220620123119"
Ou podemos utilizar os alias para voltar ou avanças nas versões das migrations
first– Volta para antes da primeira versão.prev– Volta para antes da versão atual.next– Avança para a proxima versão.latest– Avança para a última versão.
./vendor/bin/doctrine-migrations migrate prev
Para executar uma migration especifica utilizamos o comando migrate seguido do nome canônico da classe seguido da opeção --up para executar o método up e --donw para executar o método down.
./vendor/bin/doctrine-migrations execute "App\Migrations\Version20220620123119" --down
Podemos em vez de executar as migrations diretamente no banco, exportá-las para um arquivo com os comando SQL, para isto utilizamos o comando migrate com a opção --write-sql.
./vendor/bin/doctrine-migrations migrate --write-sql
Caso já tenha uma estrutura em um banco de dados e deseje gerar o migration a partir dele pode ser usado o comando dump-schema.
./vendor/bin/doctrine-migrations dump-schema
Bom era isso. T++

