Why I DON'T Use Migrations to Add #Laravel Columns

Video thumbnail

Here I want to talk about a topic I think is interesting: migrations in Laravel.

Types of migrations in Laravel
Let's remember that there are two types of migrations to give some context. There's the typical one:

$ php artisan migrate

It always starts like this. Then, in create, you indicate what you want to create (for example, the table name: payments, books, etc. — we already have that for users). And that already means you're going to create a table. You can see this here; for example, this is what we did for the payments section. If I find it… here it is. So, a structure called payments will be created.

But what happens when you want to, for example, add a column and so on? There we have a slightly different syntax.

$ php artisan make:migration add_nombre_columna_to_nombre_tabla_table

This is a little complicated. I talk about it from the perspective of being an independent developer. All the projects I do, I do myself, whether for third parties or directly for myself.

Therefore, when I want to talk to someone, I talk to myself. It's not a team project, so I have certain limitations… and I also adapt my programming style to that.

For example, I always use Git. My machine can crash—I'd regret it, of course—but I don't lose my work because it's always synchronized. I synchronize it, so to speak, with myself.

I use Git, then I go to another machine, download the changes, continue developing… and so on. This is very different when you're developing in a team of 10 people on the same project. In those cases, at some point, files may overlap, there may be conflicts, and they may have to be resolved with merge. I don't have those problems because I work alone.

It's not like I'm going to turn on two machines to work in parallel. I work on one, sync, go to the other, pull, keep working, and that's it.

The bad practice that works for me

This also applies somewhat to migrations. And this is where I recognize I may be following a bad practice.

I hate having tables scattered across multiple migrations. Especially with entities as generic and flexible as payments. For example, at first I had a product called "course." Then I wanted to also manage "books." Since my organization wasn't the best at the beginning, I have duplicate items.

This then affects other tables, like the users table. At first, I didn't sell anything, but when I started selling, I had to add more fields to users. Suppose users wasn't generated by Laravel, but by me. This applies to any entity.

What I want to conclude is that at some point you'll probably want to modify the tables. Especially to add columns or use enumerated types. You'll always want to add things.

The "Laravel" way is to create a new migration and make the changes there. That would be the correct solution... or at least the most recommended. But what happens? When you search for payments, for example, you'll see 20 migrations for that table: one where you added a column, another where you removed an enum, another where you changed the name...

I find that horrible. I don't like it.

My alternative

In my case, I prefer to have the entire structure defined in a single migration rather than multiple. But this poses a problem: if you make a change, you have to delete the entire database for the migration to be reapplied. This isn't ideal because you'd lose all the test data in development (and in production, it's impossible).

Also, I work on a shared server. I don't have access to the terminal to run these commands. I have to enter the SQL directly. As far as I know, Laravel doesn't generate that SQL directly, but you get the point.

How do I solve it?
What I do is not generate intermediate migrations. That's why I don't remember the commands very well. When I want to add a new field—for example, returned—I add it directly to the original migration. This happens a lot: sometimes you develop modules later, like one for cancellations or disputes, and you realize you need new columns.

For example, at first you have a cancellation, and then you realize you also need a dispute field. Then you integrate MercadoPago. And all that would be five different migrations... for a single table!

It's a nightmare to read like that. And if a new developer joins the team, they have to put the puzzle together in their head to understand how the table turned out.

So, what I do is directly modify the original migration. But of course, how do I replicate that in the already created database? What I do is use tools like DBgin, TablePlus, phpMyAdmin, or the one that comes with Laragon.

Manual migrations with SQL

These database management systems generate the SQL command behind the scenes. I make the change from the graphical interface (for example, adding an enum field) and then copy that SQL.

I then run it manually on my other machines… and also on the production server (where I don't have console access). In total, I use about four machines, plus production:

ALTER TABLE `file_payments`
CHANGE `payments` `payments` enum('paypal','stripe','free','other', 'google', 'apple') NOT NULL DEFAULT 'paypal';

This way, I have all my migrations organized. I read them, open them, and don't have to put together puzzles. Plus, I avoid having so many migrations.

The downside is that it's a bad practice, because Laravel is designed for you to manage the database from the framework. But as I said, it's worked for me.

Although I admit it causes problems. For example, I once defined an enum with a space at the end of the value… and that caused problems. Here in the migration, it was well-defined, but in the manually generated SQL, it wasn't. So, when you compare values, they don't match (null vs. empty, for example).

These types of errors are subtle and difficult to detect. But hey, they're things you learn over time.

I agree to receive announcements of interest about this Blog.

I'm telling you how to avoid creating an additional migration to MODIFY the table when I want to modify a table in Laravel.

- Andrés Cruz

En español