Last updated May 16, 2013. Created by mikeryan on April 20, 2011.
Edited by goldenboy, alexweber, eddie_c, joachim. Log in to edit this page.
Source fields are mapping to destination fields by making calls to $this->addFieldMapping() in your Migration class constructor. Field mappings are represented as instances of the MigrateFieldMapping class, which implements a fluent interface (or Method Chaining) akin to that of the Drupal 7 database API. This enables optional behavior to be attached to mappings, without using a long list of optional arguments on addFieldMapping().
A quick example of command chaining is given by:
<?php
// Each method returns a FieldMapping object
$this->addFieldMapping('sticky')
->description(t('Should we default this to 0 or 1?'))
->issueGroup(t('Client questions'))
->issueNumber(765736)
->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_LOW);
?>The following methods can typically be chained:
->description($description): provide a description for the mapping. This will be displayed in the Migrate UI.->defaultValue($value): provide a default value for this field.->separator($delimiter): the source string will be converted to an array using the separator as delimiter->callbacks($callbacks): define functions to be executed in order to prepare the data for that specific field.->sourceMigration(array($migrationMachineName,[...])): $migrationMachineName is an array of migrations that can act as sources for this field. The source field will be used as source ID from another migration and translated to it's destination ID. This integrates withcreateStub()in migration classes. See Chicken and Eggs->dedupe($table, $column): Search for duplicate value in $table and $column. When a duplicate value is found a counter will be appended.->issueGroup($group): Used for display in the migrate UI->issueNumber($group): Used for display in the migrate UI. Related to ticketing or tracking system.->issuePriority($group):for display in the migrate UI.->arguments($array): provide arguments for this mapping. Depends on the destination field type (ex: language, format, destination directory for files, ...). As of Migrate 2.4, using arguments is deprecated - instead, map the subfields provided by the particular destination field type.
Simple mapping
In its simplest form, a field mapping defines a straight copy of source data to the destination Drupal object:
<?php
$this->addFieldMapping('title', 'source_subject');
?>In a node migration, this indicates that when each source row is processed, the contents of its source_subject field will be copied to the node title.
It's not uncommon for fields to have the same name on both the source and destination sides. For example, when migrating nodes from a system with title and body fields, you might expect to write:
<?php
$this->addFieldMapping('title', 'title');
$this->addFieldMapping('body', 'body');
?>But, you can take a shortcut:
<?php
$this->addSimpleMappings(array('title', 'body'));
?>Similarly, when you have several destination or source fields which are not being migrated, rather than writing out an addFieldMapping() call (with issueGroup() attached) for each one, you can do it in one call:
<?php
$this->addUnmigratedDestinations(array('status', 'uid'));
$this->addUnmigratedSources(array('old_field', 'older_field', 'oldest_field'), t('Do Not Migrate'));
?>The issue group for these mappings defaults to DNM, but as you see in the source case above it can be overridden.
Provide Default values
A default value can be provided:
<?php
$this->addFieldMapping('field_age_range', 'source_age_range')
->defaultValue(t('Unknown age range'));
?>This indicates that for every source row with a NULL or empty source_age_range value, the resulting field_age_range should be set to 'Unknown age range'.
By omitting the source field, you can hard-code a value to be applied to every migrated row - in this case, we ensure that every migrated user has the "authenticated user" role:
<?php
$this->addFieldMapping('roles')
->defaultValue(DRUPAL_AUTHENTICATED_RID);
?>Convert source strings to arrays
Multiple values for a field can be migrated directly:
<?php
$this->addFieldMapping('field_tags', 'tags')
->separator(',');
?>If the source field tags contains a comma-separated list of tags (e.g., "music,tv,radio"), field_tags will be assigned an array of the individual tags (array('music', 'tv', 'radio')).
Subfields
As of Migrate 2.4, when a destination field has options which can be set to control its behavior, or requires multiple pieces of data, these can be set using subfields. For example, to set a body and its summary:
<?php
$this->addFieldMapping('body', 'body');
$this->addFieldMapping('body:summary', 'excerpt');
?>The subfields are exposed on the migration detail pages just like regular fields, and all the power of field mappings can be applied to them.
It's important to note that you must map the primary field before its subfields - this will not work:
<?php
$this->addFieldMapping('body:summary', 'excerpt');
$this->addFieldMapping('body', 'body');
?>Providing arguments
Before Migrate 2.4, to set options or additional data required passing arguments. The arguments are passed in an associative array to the field mapping arguments() method:
<?php
$this->addFieldMapping('field_image', 'image_file_name')
->arguments(array('file_function' => 'file_move', 'file_replace' => FILE_EXISTS_REPLACE));
?>Some of these field handlers provide convenience functions for building the argument list - certain IDEs will display the function documentation to help guide you in filling in the argument list:
<?php
$arguments = MigrateFileFieldHandler::arguments('', 'file_move', FILE_EXISTS_REPLACE);
$this->addFieldMapping('field_image', 'image_file_name')
->arguments($arguments);
?>A very powerful feature of arguments is the ability to pass fields from your source data through the arguments - you can do this by, instead of passing a simple value directly, passing an array with the source_field value containing the name of the source field containing the value to use. For example, file fields can have optional data such as titles associated with them, and you can migrate that data through arguments:
<?php
$arguments = MigrateFileFieldHandler::arguments('', 'file_move', FILE_EXISTS_REPLACE, NULL, array('source_field' => 'image_file_alt'));
$this->addFieldMapping('field_image', 'image_file_name')
->arguments($arguments);
?>You will find that using arguments will usually still work in Migrate 2.4 or later, but they are deprecated.
Callbacks
Sometimes you need to perform some transformation on a single field. You could do this in prepareRow(), but the simplest thing to do is add a callback to the field. This isolates the manipulation from any more complex work going on in prepareRow(), lets you apply standard PHP functions without adding anything outside of the field mapping, and also lets you share a function among multiple fields.
<?php
// Incoming text is ISO-8859
$this->addFieldMapping('field_text_stuff', 'source_text')
->callbacks('utf8_encode');
$this->addFieldMapping('field_computed_value', 'field_base_value')
->callbacks(array($this, 'computeValue'));
...
protected function computeValue($value) {
$value = $value*10 + 28.3;
return $value;
}
?>Note that the callbacks are applied to the source $row field after prepareRow() is called.
Deduping
In some cases, Drupal may require a given field be unique for every object, but the source system may not have had the same requirement. You can automatically generate deduped values in these instances. In this example, while Drupal requires that usernames be unique, the source system we're coming from did not:
<?php
$this->addFieldMapping('name', 'username')
->dedupe('users', 'name');
?>The dedupe() arguments are the Drupal table and column to check for duplicates. When a migrate-import is run, if the first record has a username of 'mike', a Drupal user with name 'mike' is created. If later another record has a username of 'mike', the dedupe handling will query users.name and discover it already exists, so it will modify the name value to 'mike_1'.
Documenting mappings
The above methods cover all the methods that affect the actual operation of data import. There are a number of other methods and techniques, however, which aid in documenting your migration. If you have enabled the migrate_ui module, clicking on a migration name on the Migrate dashboard brings you to a page documenting the migration - the source, the destination, and especially the mappings. Regular review of this page can help ensure nothing gets missed.
Every field mapping has an Issue Group associated with it - on the migration information page, each group is rendered on a vertical tab named "Mapping: <group>". If you don't provide a group explicitly using issueGroup() (see below), it defaults to "Done".
Now, it's a rare migration where you use every available source field, and populate every available Drupal field directly from a source field. Usually some source data will simply be ignored, while some Drupal fields will be allowed to fallback to their defaults. You could simply not map these at all, but it is a good practice to make these decisions explicit - to clearly indicate that someone has decided these fields will not be mapped. A helpful feature of migrate_ui is that it highlights on the Destination and Source tabs any fields that have no mappings - by explicitly marking fields that are not to be migrated, you can distinguish the fields you know you don't want to migrate from those you haven't dealt with yet, or new fields that have appeared and need to be dealt with. So, a good convention is to create NULL mappings within a group named "Do Not Migrate" (or "DNM", if you're a lazy typist).
<?php
// Unmapped destination fields
$this->addFieldMapping('status')
->issueGroup(t('DNM'));
$this->addFieldMapping('uid')
->issueGroup(t('DNM'));
// Unmapped source fields
$this->addFieldMapping(NULL, 'obsolete_data')
->issueGroup(t('DNM'));
?>Now, developing a complex migration process may take weeks or months - you are not going to know exactly what is being migrated and how when you initially write your migration class. Consider this example:
<?php
$this->issuePattern = 'http://drupal.org/node/:id:';
...
$this->addFieldMapping('field_ingredients')
->issueGroup(t('Client issues'))
->description(t('Where will the ingredient data come from?'))
->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
->issueNumber(770064);
?>The Migrate module was created in the context of consultants developing migrations for clients, so our usual convention is to have a "Client issues" group for anything the client needs to address (usually providing information on how or whether they want a field to be migrated) and an "Implementor issues" group for mappings which are fully specified but haven't been completely implemented yet. Note that descriptions (displayed on the migration detail page) can be added to any mapping, and are recommended for all but the most trivial cases. The issuePriority defaults to MigrateFieldMapping::ISSUE_PRIORITY_OK - if any other value, it will be highlighted on the detail page. The values for priorities are:
- ISSUE_PRIORITY_OK
- ISSUE_PRIORITY_LOW
- ISSUE_PRIORITY_MEDIUM
- ISSUE_PRIORITY_BLOCKER
Finally, note the issuePattern setting on the migration class and the issueNumber on the field mapping. This can be used to link to a ticket or issue in an issue-tracking system such as Unfuddle or Jira - the issueNumber is plugged into the :id: placeholder in the issuePattern to generate the link.
Removing field mappings
Usually, you will be implementing multiple Migration classes which share some common attributes (such as $this->issuePattern). To share such commonality, you would derive an intermediate abstract class directly from Migration, then derive each of your concrete classes from the intermediate class. In some cases, you may even be able to share a number of field mappings among multiple migrations by including them in the intermediate class. But, what if you have a mapping that applies to 9 out of your 10 migrations (e.g., you have a taxonomy reference field_tags on all but one content type)? What you can do is add the field mapping in the common constructor, then remove it in the one exception:
<?php
abstract class CommonMigration extends Migration {
public function __construct() {
parent::__construct();
...
$this->addFieldMapping('field_tags', 'tags');
...
}
}
class PageMigration extends CommonMigration {
public function __construct() {
parent::__construct();
...
$this->removeFieldMapping('field_tags');
...
}
}
?>