2

We have a Form plugin and an Email plugin. Users can setup the Email Plugin to send off notification emails when a particular Event happens. Some users setup a notification email to be sent when a Form is submitted from the Form plugin.

In a recent upgrade, we needed to migrate all content to the Form plugins entries table. We did so using the saveFormEntry method in the Form plugin service layer. This means, that each time that we call saveFormEntry in the migration, if somebody has the Email plugin to send out a notification when a Form Entry is saved, the Event fires and the Email plugin tries to do it's thing.

In our case, this just killed the migration script. Setting the Email plugin to disabled before running the Form migration get's around the issue.

So, what is the best way to handle Events that get triggered during a migration?

One method we've thought of is to just programmatically set the Email plugin to disabled before running our migration. That might work for us, but what if other plugins need to do the same? Do we then need to somehow try to communicate this to everyone who might trigger an email event in a migration?

Another idea we've discussed is what if Craft had something like devMode for while migrations were running. So we could just plan into our Email plugin to only run events when migrationMode = false.

As a general question, are there any benefits to having Events enabled during migrations? Are there any benefits to having plugins enabled during migrations? I'm sure this isn't simply a black and white question, but I'm new to thinking about the complexity of having multiple plugins triggering behaviors in other plugins and don't really like the idea that somebody could accidentally trigger several emails to be sent out while trying to run a migration.

Any thoughts on where this is best handled and if we can take any steps to make our code more robust in handling scenarios like these would be appreciated.

1

In general it’s best to avoid calling service methods from migration files in the first place. The reason for that is, your services should be able to assume that they’re always working with the latest schema design.

Imagine if a month from now you decide you need to add a new column to your formentries table (or whatever it is). The associated record, model, and service all get updated to work with this new column’s data. And you write a migration that adds the column:

$this->addColumn('formentries', 'newColumnName', ColumnType::Varchar);

You test and everything appears to work great, and then release it.

Then someone who was running an old version of your plugin tries to update. Before Craft runs the new migration that adds the new formentries column, it still needs to run your old migration that calls your saveFormEntry() service method. Except that saveFormEntry is now assuming that the new column already exists, and depending on your code, either they run into a validation error (so no form entries get saved) or a MySQL error (because your record is trying to save on a column that doesn’t exist yet).

So, in the long run you’ll be better off if you keep all DB-related changes contained to your migration file.

That said, Craft does cheat a little here - this is exactly why we have the concept of “breakpoint updates” at our disposal. When we mark a release as a breakpoint, it prevents anyone from updating to a version after it, until they have updated to it. So if we do need to call service methods in a migration, then we just need to remember to set a breakpoint at some point before we start making more DB schema changes.

Plugins will also be able to set breakpoints when Craft 3 comes along. Because of that, this is still a relevant question. I’m thinking that the safest thing to do here is to simply disable event dispatching altogether during updates. A plugin should be in full control over what code gets run when it’s updating itself, with no chance of another plugin intervening.

I don’t think we have a way to disable Yii’s event system altogether (and probably shouldn’t even if we could), but we could at the very least prevent craft()->on() from doing its thing during update requests. Maybe even add a new argument to it that can be set to true if the event should get fired even if it’s an update request, in case there are any use cases that I’m overlooking.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.