Back to Blog
Apr 19th, 2022

Clean up your migrations!

Written by bocharsky-bw, and AshMiass

Edit
Clean up your migrations!

Database migrations are a great way to safely update your database schema, and so it's very useful for production where you do not want to lose the data you have. If you want to know more about migrations - we have a screencast about it, check it out!

But here, I want to give you a tip on how to clean up your migrations... if you find your migrations/ directory growing and growing... like we did! First of all, this is mostly a problem for local development: on production, we don't really care how many migration files were executed in the past.

But while developing locally, to set things up, we recommend creating your database and then running the doctrine:migrations:migrate command so that you have exactly what's on production, including a migration_versions table. This is better than running the doctrine:schema:update command, which then makes it hard to test new migrations.

But, if you have a lot of old migration files, this can start to get kinda slow! On SymfonyCasts, we had 49. And so, while it's TOTALLY optional, if a giant migrations/ folder bothers you, you CAN clean it up! Also, congratulations on having a successful site that has lived long enough to need this.

Generating a Complete Single Migration

Tip

Now you can achieve this with built-in features in a much easier way than described in the beginning of this blog post!

First of all, delete all your migrations:

rm -rf migrations/*

Now, dump the current schema into a new single migration:

symfony console doctrine:migrations:diff --from-empty-schema

The bundle provides one more command that helps to "roll up" the migrations by deleting all tracked versions and insert the one version that exists:

symfony console doctrine:migrations:rollup

You just need to deploy the code to production and run this command manually. But if you have automated deployment that runs migrations - it might not fit you. For automated deploy, continue reading this blog post from Skipping the Re-generated Migration on Production.

P.S. Thanks to Christophe Coevoet for pointing it in the comments.

Of course, we're going to do this locally first, so, run:

rm -rf migrations/*

to drop all the migrations. Then, make sure you don't have important data you care about in your local database, then drop its schema completely with:

symfony console doctrine:schema:drop --force

Tip

The symfony console command is the same as bin/console but it allows environment variables to be injected if you're using the Docker integration.

Now let's generate a new migration with MakerBundle:

symfony console make:migration

Double-check the queries inside the new file to make sure the new migration looks good.

But we can't just commit these changes and deploy to production... because the database server will try to run this command and recreate the tables we already have on production! There's almost nothing less fun than migrations failing during a deploy! How can we work around it without manually messing with the production database?

Skipping the Re-generated Migration on Production

With a trick: rename the migration you just created to one that was already executed on production. You can check the latest migration name you dropped by running:

git status

For example, if you had a migration file called Version20220415102030.php before, rename the new migration file that was just generated to this exact name, and don't forget to change the class name inside the file accordingly. This will allow you to deploy to production, but this migration won't be executed (it will be skipped) because it was already executed in the past.

Removing the Previously Executed Migrations from Production

But we can improve this a bit more. Because if you executed this migration right now - it would say something like:

[WARNING] You have X previously executed migrations in the database that are not registered migrations.

Rude! This is because the migration_versions table on production will now show that it executed a bunch of migrations in the past... but these migration files don't exist anymore! We could remove them manually from the production database but... yikes! Manually running a DELETE query on the production database is less fun than taking your cat to the vet.

Generate a blank migration instead:

symfony console doctrine:migration:generate

Then, open it, and add a new SQL statement at the end:

final class Version20220415102031 extends AbstractMigration
{
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs

        $this->addSql('DELETE FROM migration_versions WHERE version NOT LIKE "%20220415102030" AND version NOT LIKE "%20220415102031"');
    }

    // ...
}

Where 20220415102031 is the number of the blank migration we just generated, and 20220415102030 is the number of the other complete migration we have. Make sure you replace those numbers with yours!

And we're ready! Try it locally first:

symfony console doctrine:migration:migrate

It will still show the warning, but only once. To double-check, you may want to look at migration_versions table in your local database to make sure we have only those 2 migrations there.

Now, it's safe to deploy it to production and celebrate with a clean migrations/ directory!

13 Comments

Sort By
Login or Register to join the conversation
Default user avatar Christophe Coevoet 2 years ago

this can actually be simplified a lot (without the need to manually dump the schema and without the need to manually change metadata about migrated versions) by using the built-in feature for that: the doctrine:migrations:dump-schema and doctrine:migrations:rollup commands

2 | Reply |

Hey Christophe!

Whoops, I didn't know about this out-of-the-box feature :) Well, now I know, thank you! I'll update the post mentioning about this short way.

Cheers!

1 | Reply |
Jenne avatar Jenne 1 year ago edited

Getting old but while https://github.com/doctrine/migrations/issues/1163 is still a thing; dump-schema does not add FK's. It might be advisable to use symfony console doctrine:migrations:diff --from-empty-schema instead of symfony console doctrine:migrations:dump-schema.

1 | Reply |

Thanks for sharing that workaround

Cheers!

| Reply |
Bojan avatar Bojan 1 year ago

Thank you for the nice post, and also for the doctrine:migrations:rollup I was not aware of it.

Probably worth pointing out, this is safe if migrations contain only schema changes. If some of the migrations contain any data changes, like
$this->addSql('INSERT INTO my_table...
we'd lose that data in dev environment.

| Reply |

Thank you @Bojan

You're right, if your migration will change data or will add a non-nullable field, you have to be careful and create a "safe" migration. Ryan talks just a bit about it here https://symfonycasts.com/screencast/symfony5-doctrine/bad-migrations

1 | Reply |
Hugo_Derre avatar Hugo_Derre 1 year ago

It worked perfectly on my production environment.
There are only good articles on this site, thanks for what you do!

| Reply |

Hey Hugo,

You're welcome! And thanks for the feedback, I'm happy to hear it was useful :)

Cheers!

| Reply |
Rufnex avatar Rufnex 2 years ago

Is there also a way to create a dump-schema for existing database tables? if i run this it tells me "Your database schema does not contain any tables."

I have a connection to an existing databse. Is ther a best practice for update, alter, delete ... in this case?

| Reply |

Hey Rufnex,

Hmm, what do you mean? Do you have some legacy tables in the DB that are not mapped to entities? If so, I'm afraid that you would need to do that dump manually, using the MySQL Workbench UI app or executing commands directly in your CLI.

Well, I suppose you're talking about the "doctrine:migrations:dump-schema" command. You can see options for any Symfony command by adding a "--help" to it when executing. For example, it has the "--conn" option and so if you have a special connection - you can try to specify it here I suppose, but I'm not sure this command will help you with the schema of legacy tables that are not mapped to entities anyway.

I hope this helps!

Cheers!

| Reply |

I have to migrate a complex database with 65 tables and tones of constrains etc .. so i tried the old way to make an migration.
symfony console doctrine:mapping:import "App\Entity" annotation --path=src/Entity

But after i played a while . i decided to make the whole stuff by hand and create the entities with the maker tool.

its a boring task.

| Reply |

Hi Victor,

thanks for answer. I figured it now out and have to do the ORM mapping for my existing database by hand. Unforunatly its a big one with 65 tables and some long structure ;( Bad work ;o)

greetings

| Reply |

Hey Rufnex,

Oh, I see... Yeah, legacy websites require some work to migrate properly. And yeah, you have A LOT of tables in your project, so it won't be easy (fast).

Anyway, I'm happy to hear you found a strategy to move forward for you, good luck with it!

Cheers!

1 | Reply |

Delete comment?

Share this comment

astronaut with balloons in space

"Houston: no signs of life"
Start the conversation!