Migrations
Migrations are PHP classes that make one-time changes to the system.
For the most part, migrations in Craft work similarly to Yii’s implementation (opens new window), but unlike Yii, Craft manages three different types of migrations:
- App migrations – Craft’s own internal migrations.
- Plugin migrations – Each installed plugin has its own migration track.
- Content migrations – Your Craft project itself can have migrations, too.
# Creating Migrations
If your Craft install is running from a Vagrant box, you will need to SSH into the box to run these commands.
To create a new migration for your plugin or project, open up your terminal and go to your Craft project:
cd /path/to/project
Then run the following command to generate a new migration file for your plugin or project:
php craft migrate/create my_migration_name --plugin=my-plugin-handle
Enter yes
at the prompt, and a new migration file will be created for you. You can find it at the file path output by the command.
If this is a plugin migration, increase your plugin’s schema version (opens new window), so Craft knows to check for new plugin migrations as people update to your new version.
# What Goes Inside
Migration classes contain methods: safeUp() (opens new window) and safeDown() (opens new window). safeUp()
is run when your migration is applied, and safeDown()
is run when your migration is reverted.
You can usually ignore the safeDown()
method, as Craft doesn’t have a way to revert migrations from the control panel.
You have full access to Craft’s API (opens new window) from your safeUp()
method, but plugin migrations should try to avoid calling the plugin’s own API here. As your plugin’s database schema changes over time, so will your APIs assumptions about the schema. If an old migration calls a service method that relies on database changes that haven’t been applied yet, it will result in a SQL error. So in general you should execute all SQL queries directly from your own migration class. It may feel like you’re duplicating code, but it will be more future-proof.
# Manipulating Database Data
Your migration class extends craft\db\Migration (opens new window), which provides several methods for working with the database. It’s better to use these than their craft\db\Command (opens new window) counterparts, because the migration methods are both simpler to use, and they’ll output a status message to the terminal for you.
// Bad:
$this->db->createCommand()
->insert('{{%mytablename}}', $rows)
->execute();
// Good:
$this->insert('{{%mytablename}}', $rows);
craft\helpers\MigrationHelper (opens new window) provides several helpful methods as well:
- dropForeignKeyIfExists() (opens new window) removes a foreign key if it exists, without needing to know its exact name.
- dropIndexIfExists() (opens new window) removes an index if it exists, without needing to know its exact name.
- dropTable() (opens new window) drops a table, along with any foreign keys that reference it (some of which your plugin might not even be aware of).
The yii\db\Migration::insert() (opens new window), batchInsert() (opens new window), and update() (opens new window) migration methods will automatically insert/update data in the dateCreated
, dateUpdated
, uid
table columns in addition to whatever you specified in the $columns
argument. If the table you’re working with does’t have those columns, make sure you pass false
to the $includeAuditColumns
argument so you don’t get a SQL error.
craft\db\Migration (opens new window) doesn’t have a method for selecting data, so you will still need to go through Yii’s Query Builder (opens new window) for that.
use craft\db\Query;
$result = (new Query())
// ...
->all();
# Logging
If you want to log messages in your migration code, echo it out rather than calling Craft::info() (opens new window):
echo " > some note\n";
If the migration is being run from a console request, this will ensure the message is seen by whoever is executing the migration, as the message will be output into the terminal. If it’s a web request, Craft will capture it and log it to storage/logs/
just as if you had used Craft::info()
.
# Executing Migrations
You can have Craft apply your new migration from the terminal:
php craft migrate/up --plugin=my-plugin-handle
Or you can have Craft apply all new migrations across all migration tracks:
php craft migrate/all
Craft will also check for new plugin migrations on control panel requests, for any plugins that have a new schema version (opens new window), and content migrations can be applied from the control panel by going to Utilities → Migrations.
# Plugin Install Migrations
Plugins can have a special “Install” migration which handles the installation and uninstallation of the plugin. Install migrations live at migrations/Install.php
alongside normal migrations. They should follow this template:
<?php
namespace mynamespace\migrations;
use craft\db\Migration;
class Install extends Migration
{
public function safeUp(): bool
{
// ...
}
public function safeDown(): bool
{
// ...
}
}
You can give your plugin an install migration with the migrate/create
command if you pass the migration name “install
”:
php craft migrate/create install --plugin=my-plugin-handle
When a plugin has an Install migration, its safeUp()
method will be called when the plugin is installed, and its safeDown()
method will be called when the plugin is uninstalled (invoked by the plugin’s install() (opens new window) and uninstall() (opens new window) methods).
It is not a plugin’s responsibility to manage its row in the plugins
database table. Craft takes care of that for you.
# Setting Default Project Config Data
If you want to add things to the project config on install, either directly or via your plugin’s API, be sure to only do that if the incoming project config YAML doesn’t already have a record of your plugin.
public function safeUp(): bool
{
// ...
// Don’t make the same config changes twice
if (Craft::$app->projectConfig->get('plugins.my-plugin-handle', true) === null) {
// Make the config changes here...
}
}
That’s because there’s a chance that your plugin is being installed as part of a project config sync, and if its install migration were to make any project config changes of its own, they would overwrite all of the incoming project config YAML changes.
The reverse could be useful if you need to make changes when your plugin is uninstalled:
public function safeUp(): bool
{
// ...
}
public function safeDown(): bool
{
// ...
// Don’t make the same config changes twice
if (Craft::$app->projectConfig->get('plugins.my-plugin-handle', true) !== null) {
// Make the config changes here...
}
}