Generating Migrations

Doctrine can generate blank migrations for you to modify or it can generate functional migrations for you by comparing the current state of your database schema to your mapping information.

Generating Blank Migrations

To generate a blank migration you can use the generate command:

$ ./vendor/bin/doctrine-migrations generate

Diffing Using the ORM

If you are using the ORM, you can modify your mapping information and have Doctrine generate a migration for you by comparing the current state of your database schema to the mapping information that is defined by using the ORM. To test this functionality, create a new User entity located at lib/MyProject/Entities/User.php.

1<?php namespace MyProject\Entities; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Table; #[Entity] #[Table(name: 'users')] class User { #[Id] #[Column(type: 'integer')] #[GeneratedValue] private $id; #[Column(type: 'string', nullable: true)] private $username; public function setId(int $id) { $this->id = $id; } public function getId(): ?int { return $this->id; } public function setUsername(string $username): void { $this->username = $username; } public function getUsername(): ?string { return $this->username; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

Now when you run the diff command it will generate a migration which will create the users table:

$ ./vendor/bin/doctrine-migrations diff
Generated new migration class to "/data/doctrine/migrations-docs-example/lib/MyProject/Migrations/Version20180601215504.php"

To run just this migration for testing purposes, you can use migrations:execute --up 'MyProject\Migrations\Version20180601215504'

To revert the migration you can use migrations:execute --down 'MyProject\Migrations\Version20180601215504'

Take a look at the generated migration:

Notice how the table named example_table that we created earlier in the Managing Migrations chapter is being dropped. This is because the table is not mapped anywhere in the Doctrine ORM and the diff command detects that and generates the SQL to drop the table. If you want to ignore some tables in your database take a look at Ignoring Custom Tables chapter.

1<?php declare(strict_types=1); namespace MyProject\Migrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20180601215504 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 $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); $this->addSql('DROP TABLE example_table'); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL COLLATE latin1_swedish_ci, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); $this->addSql('DROP TABLE users'); } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

Now you are ready to execute your diff migration:

$ ./vendor/bin/doctrine-migrations migrate

                    My Project Migrations


WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y
Migrating up to MyProject\Migrations\Version20180601215504 from MyProject\Migrations\Version20180601193057

  ++ migrating MyProject\Migrations\Version20180601215504

     -> CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB
     -> DROP TABLE example_table

  ++ migrated (took 75.9ms, used 8M memory)

  ------------------------

  ++ finished in 84.3ms
  ++ used 8M memory
  ++ 1 migrations executed
  ++ 1 sql queries

The SQL generated here is the exact same SQL that would be executed if you were using the orm:schema-tool command. This just allows you to capture that SQL and maybe tweak it or add to it and trigger the deployment later across multiple database servers.

Diffing Without the ORM

Internally the diff command generates a Doctrine\DBAL\Schema\Schema object from your entities metadata using an implementation of Doctrine\Migrations\Provider\SchemaProviderInterface. To use the Schema representation directly, without the ORM, you must implement this interface yourself.

The SchemaProviderInterface only has one method named createSchema. This should return a Doctrine\DBAL\Schema\Schema instance that represents the state to which you'd like to migrate your database.

1<?php use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Provider\SchemaProviderInterface; final class CustomSchemaProvider implements SchemaProviderInterface { public function createSchema() { $schema = new Schema(); $table = $schema->createTable('users'); $table->addColumn('id', 'integer', [ 'autoincrement' => true, ]); $table->addColumn('username', 'string', [ 'notnull' => false, ]); $table->setPrimaryKey(array('id')); return $schema; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

The StubSchemaProvider provided with the migrations library is another option. It simply takes a schema object to its constructor and returns it from createSchema.

1<?php use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Provider\StubSchemaProvider; $schema = new Schema(); $table = $schema->createTable('users'); $table->addColumn('id', 'integer', [ 'autoincrement' => true, ]); $table->addColumn('username', 'string', [ 'notnull' => false, ]); $table->setPrimaryKey(array('id')); $provider = new StubSchemaProvider($schema); $provider->createSchema() === $schema; // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

By default the Doctrine Migrations command line tool will only add the diff command if the ORM is present. Without the ORM, you'll have to add the diff command to your console application manually and set your custom schema provider to the dependency factory, which will be passed to the the diff command's constructor. Take a look at the Custom Integration chapter for information on how to setup a custom console application.

1<?php use Doctrine\Migrations\Tools\Console\Command\DiffCommand; $schemaProvider = new CustomSchemaProvider(); $dependencyFactory->setDefinition(SchemaProvider::class, static fn () => $schemaProvider); /** @var Symfony\Component\Console\Application */ $cli->add(new DiffCommand($dependencyFactory); // ... $cli->run();
2
3
4
5
6
7
8
9
10
11
12
13

With the custom provider in place the diff command will compare the current database schema to the one provided by the SchemaProviderInterface implementation. If there is a mismatch, the differences will be included in the generated migration just like the ORM examples above.

Formatted SQL

You can optionally pass the --formatted option if you want the dumped SQL to be formatted. This option uses the doctrine/sql-formatter package so you will need to install this package for it to work:

$ composer require doctrine/sql-formatter

Ignoring Custom Tables

If you have custom tables which are not managed by Doctrine you will need to tell Doctrine to ignore these tables. Otherwise, everytime you run the diff command, Doctrine will try to drop those tables. You can configure Doctrine with a schema filter.

1$connection->getConfiguration()->setSchemaAssetsFilter(static function (string|AbstractAsset $assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return (bool) preg_match("~^(?!t_)~", $assetName); });
2
3
4
5
6
7

With this expression all tables prefixed with t will ignored by the schema tool.

If you use the DoctrineBundle with Symfony you can set the schema_filter option in your configuration. You can find more information in the documentation of the DoctrineMigrationsBundle.

Merging Historical Migrations

If you have many migrations, which were generated by successive runs of the diff command over time, and you would like to replace them with one single migration, you can delete (or archive) all your "historical" migration files and run the diff command with the --from-empty-schema option. It will generate a full migration as if your database was empty. You can then use the rollup command to synchronize the version table of your (already up-to-date) database.

Next Chapter: Custom Configuration