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++