Clean up your migrations!
Written by bocharsky-bw, and AshMiass
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
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!
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
.
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.
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
It worked perfectly on my production environment.
There are only good articles on this site, thanks for what you do!
Hey Hugo,
You're welcome! And thanks for the feedback, I'm happy to hear it was useful :)
Cheers!
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?
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!
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.
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
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!
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
anddoctrine:migrations:rollup
commands