Migrate 2.5 has been released, consolidating several enhancements and bug fixes since Migrate 2.4 was released in June.
Class registration
The most important changes to be aware of are around the registration of migration and handler classes. In the past, you simply defined the classes you needed, and Migrate would (by scanning the class registry and instantiating reflection classes) detect their presence - Migration classes would be registered (recorded in the migrate_status table) on a cache clear, and Handler classes made available to be called during migration processes. It turns out that this discovery process could fail badly in some circumstances - specifically, when the registry referenced a class which was derived from a class not in the registry, instantiating the reflection class caused a fatal error. The particular trigger where this was diagnosed involved the Field collection module, which defines a custom Views handler (derived from a views-defined class). If field_collection was enabled and views was not, when Migrate scanned the registry and looked at the field_collection views handler class to determine if it was derived from Migration, instantiating the reflection class caused a fatal error. Since we were doing this in hook_flush_caches(), the problem had site-wide consequences.
So, as of Migrate 2.5, clearing the cache does not cause any registration to happen. The previous auto-registration technique can still be used (as long as your environment doesn't have the triggering condition), but it is a distinct operation. From the command line:
drush migrate-auto-register
will automatically detect classes derived from Migration. There is also a button on the Registration tab under Migrate in the UI to perform the same operation.
If you are facing the fatal error problem, or want to make sure you won't have to face it, auto-registration can be disabled on the Registration tab. With auto-registration disabled, migration classes must be registered entirely through your code using one of the following techniques (which will do no harm with auto-registration enabled, and should be considered a best practice in general):
registerMigration
- If you've implemented dynamic migrations before, you're already familiar with this, and you don't need to change anything for it to continue to work. A migration class can be registered by calling<?php MigrationBase::registerMigration('MyClassName', 'MyMachineName', $arguments); ?>
hook_migrate_api
- You can now statically register your migrations via hook_migrate_api(), and going forward you should always do this. If you have previously been defining, for example, MyUserMigration and MyArticleMigraton and expecting migrations with the names MyUser and MyArticle to appear, you should now register them like this:<?php function example_migrate_api() { $api = array( 'api' => 2, 'migrations' => array( 'MyUser' => array('class_name' => 'MyUserMigration'), 'MyArticle' => array('class_name' => 'MyArticleMigration'), ), ); return $api; } ?>
Destination and field handlers can (and should) now be registered via hook_migrate_api() as well:<?php function example_migrate_api() { $api = array( 'api' => 2, 'destination handlers' => array('MyNodeDestinationHandler'), 'field handlers' => array('MyCustomFieldHandler'), ); return $api; } ?>
Other key enhancements in Migrate 2.5
prepareKey()
In some cases, the source data for a migration may not contain a good unique column or combination of columns to serve as a key. One might expect to be able to construct such a key in prepareRow(), but that gets called too late (after the key has been used to determine whether the row has already been processed, in particular). So, we've introduced a new Migration method, prepareKey(). Implement this in your Migration class:
<?php
// We've passed a two-column source key to MigrateSQLMap, 'user_id' and 'my_id'.
// 'user_id' is to be used as-is, but my_id is constructed from other source data.
public function prepareKey($source_key, $row) {
// Construct the custom ID value, saving in the data row
$row->my_id = $row->term1_id . '_' . $row->term2_id;
// Build the key array and return it
$key = array();
$key['user_id'] = $row->user_id;
$key['my_id'] = $row->my_id;
return $key;
}
?>
Note that we did not use the $source_key parameter, which is the source key array passed to MigrateSQLMap. You can usually ignore this - it's there primarily to support the default implementation, which simply populates the returned key array directly from $row.
queueMessage()
Another issue with the timing of prepareRow(), in this case getting called too early for the obvious thing to happen, is generating messages for a given source row. From prepare() or complete(), or a field or destination handler, you can call saveMessage() to record an exception (whether error or informational) in the migration's message table, tied to the source record currently being processed. This doesn't work in prepareRow(), because during processing any existing messages for the current source row are cleared in the main import loop, after prepareRow() was called. To get around this, call queueMessage() (same parameters as saveMessage()) from prepareRow(), and your message will be saved and sent to the message table when processing on the item is done.
Rollback action
Sometimes, you want to map an incoming data item to a matching item that already exists in the destination instead of either ignoring it or creating a new instance. For example, migrate_d2d lets you setup role mappings - the role named "admin" on your legacy site should be equivalent to the "administrator" role on the destination site. If you set up your migration so the map table contains this mapping, by default when you rollback the migration the role on the destination side (which existed before your migration) will get deleted, which is undesirable to say the least. To prevent this, you can specify a rollback action (possible values are MigrateMap::ROLLBACK_DELETE and MigrateMap::ROLLBACK_PRESERVE). To prevent deletion on rollback for an entire migration, in your migration constructor do:
<?php
$this->defaultRollbackAction = MigrateMap::ROLLBACK_PRESERVE;
?>
To choose row-by-row whether an item should be deleted or preserved on rollback, in prepareRow() or prepare():
<?php
if ($mapped_to_existing) {
$this->rollbackAction = MigrateMap::ROLLBACK_PRESERVE;
}
else {
$this->rollbackAction = MigrateMap::ROLLBACK_DELETE;
}
?>
Elsewhere in the Migrate universe
WordPress Migrate 2.1 has been released - it has mostly minor tweaks and bug fixes since the 2.0 release in June, plus a revamping of the UI to make use of vertical tabs (done by Acquia's Drupal Gardens team in the process of incorporating this functionality into Gardens).
Work continues towards the initial full release of migrate_d2d. The main sticking point at the moment is support for CCK in Drupal 5.
Migrate Extras 2.5 has also been released. No further development is planned for Migrate Extras - going forward, support for contributed modules should always go into the modules themselves.
Future
In my next blog post, I'll discuss the road forward for Migrate.