Database migrations are an immensely useful part of any framework, they allow you to automatically update the schema and data of your application, and keep a record of the changes that have been made over time. In this post I’m specifically talking about migrations in Yii2.
In the past I’ve often found it useful to call upon my models to do some heavy lifting in my migrations, take the example below into consideration.
Here I’ve added a new column to my
user table which contains a generated hash. This seems like a good solution on the surface, The code is fairly DRY and easy enough to understand.
The problem only arises later on when we add new columns to our user table
Looks fine, right? it runs, and can be reversed. So whats the problem? Before I get to that.. I need to clarify my main assumption;
When adding a new column to a model, we not only create a migration, but we also update the validation rules on our model, preferably we regenerate a base model.
In the above example we would at least added some rules, whether they where generated or manually defined.
This is where the problem occurs, imagine a new developer joined the project, or you simply wanted to clean out your development environments, or deploy a new instance; all of the aforementioned scenarios have one thing in common, starting with a fresh database.
So you run
yii migrate up it begins to apply migrations in the correct order and then..
This is happening because calling
$user->save() internally calls
$user->validate() which then processes the validation rules we’ve defined for our model.
So you’re probably thinking; the fix for this is just to disable validation by calling
$user->save(false) instead, that would fix this particular issue but other ones will quickly crop up.
For example, if your model takes advantage of events like
ActiveRecord::EVENT_BEFORE_SAVE etc. It could be referencing columns that don’t exist while migrations are being applied and you will get a similar error as the one above.
In conclusion it’s best to avoid using your models in migrations as there could be unforeseen problems down the road.
Finally, here’s my proposed implementation of the original migration:
This may not be as easy to read as the original, but it is decoupled from the ActiveRecord.