You are here

beer.inc in Migrate 7.2

Same filename and directory in other branches
  1. 6.2 migrate_example/beer.inc

A basic example of using the Migrate module to import taxonomy, users, nodes, and comments.

The basic idea is

  • The users in the source application are listed in the migrate_example_beer_account table and are transformed into Drupal users.
  • Drupal "beer" nodes describe beers; The information to create the nodes comes from the migrate_example_beer_node table.
  • Taxonomy terms for the beer nodes (ale, pilsner) come from the migrate_example_beer_topic table and they are applied to nodes using the source information in the migrate_example_beer_topic_node table.
  • Comments to be attached to the beer nodes are described in the source migrate_example_beer_comment table.

We will use the Migrate API to import and transform this data and turn it into a working Drupal site.

File

migrate_example/beer.inc
View source
<?php

/**
 * @file
 * A basic example of using the Migrate module to import taxonomy, users, nodes,
 * and comments.
 *
 * The basic idea is
 * - The users in the source application are listed in the
 *   migrate_example_beer_account table and are transformed into Drupal users.
 * - Drupal "beer" nodes describe beers; The information to create the nodes
 *   comes from the migrate_example_beer_node table.
 * - Taxonomy terms for the beer nodes (ale, pilsner) come from the
 *   migrate_example_beer_topic table and they are applied to nodes using the
 *   source information in the migrate_example_beer_topic_node table.
 * - Comments to be attached to the beer nodes are described in the source
 *   migrate_example_beer_comment table.
 *
 * We will use the Migrate API to import and transform this data and turn it
 * into a working Drupal site.
 */

/**
 * To define a migration process from a set of source data to a particular
 * kind of Drupal object (for example, a specific node type), you define
 * a class derived from Migration. You must define a constructor to initialize
 * your migration object.
 *
 * For your classes to be instantiated so they can be used to import content,
 * you must register them - look at migrate_example.migrate.inc to see how
 * registration works. Right now, it's important to understand that each
 * migration will have a unique "machine name", which is displayed in the UI
 * and is used to reference the migration in drush commands.
 *
 * In any serious migration project, you will find there are some options
 * which are common to the individual migrations you're implementing. You can
 * define an abstract intermediate class derived from Migration, then derive
 * your individual migrations from that, to share settings, utility functions,
 * etc.
 */
abstract class BasicExampleMigration extends Migration {

  // A Migration constructor takes an array of arguments as its first parameter.
  // The arguments must be passed through to the parent constructor.
  public function __construct($arguments) {
    parent::__construct($arguments);

    // With migrate_ui enabled, migration pages will indicate people involved in
    // the particular migration, with their role and contact info. We default the
    // list in the shared class; it can be overridden for specific migrations.
    $this->team = array(
      new MigrateTeamMember('Liz Taster', 'ltaster@example.com', t('Product Owner')),
      new MigrateTeamMember('Larry Brewer', 'lbrewer@example.com', t('Implementor')),
    );

    // Individual mappings in a migration can be linked to a ticket or issue
    // in an external tracking system. Define the URL pattern here in the shared
    // class with ':id:' representing the position of the issue number, then add
    // ->issueNumber(1234) to a mapping.
    $this->issuePattern = 'http://drupal.org/node/:id:';
  }

}

/**
 * There are four essential components to set up in your constructor:
 *  $this->source - An instance of a class derived from MigrateSource, this
 *    will feed data to the migration.
 *  $this->destination - An instance of a class derived from MigrateDestination,
 *    this will receive data that originated from the source and has been mapped
 *    by the Migration class, and create Drupal objects.
 *  $this->map - An instance of a class derived from MigrateMap, this will keep
 *    track of which source items have been imported and what destination
 *    objects they map to.
 *  Field mappings - Use $this->addFieldMapping to tell the Migration class what
 *    source fields correspond to what destination fields, and additional
 *    information associated with the mappings.
 */
class BeerTermMigration extends BasicExampleMigration {
  public function __construct($arguments) {
    parent::__construct($arguments);

    // Human-friendly description of your migration process. Be as detailed as
    // you like.
    $this->description = t('Migrate styles from the source database to taxonomy terms');

    // In this example, we're using tables that have been added to the existing
    // Drupal database but which are not Drupal tables. You can examine the
    // various tables (starting here with migrate_example_beer_topic) using a
    // database browser such as phpMyAdmin.
    // First, we set up a query for this data. Note that by ordering on
    // style_parent, we guarantee root terms are migrated first, so the
    // parent_name mapping below will find that the parent exists.
    $query = db_select('migrate_example_beer_topic', 'met')
      ->fields('met', array(
      'style',
      'details',
      'style_parent',
      'region',
      'hoppiness',
    ))
      ->orderBy('style_parent', 'ASC');

    // Create a MigrateSource object, which manages retrieving the input data.
    $this->source = new MigrateSourceSQL($query);

    // Set up our destination - terms in the migrate_example_beer_styles
    // vocabulary (note that we pass the machine name of the vocabulary).
    $this->destination = new MigrateDestinationTerm('migrate_example_beer_styles');

    // Create a map object for tracking the relationships between source rows
    // and their resulting Drupal objects. We will use the MigrateSQLMap class,
    // which uses database tables for tracking. Pass the machine name (BeerTerm)
    // of this migration to use in generating map and message table names.
    // And, pass schema definitions for the primary keys of the source and
    // destination - we need to be explicit for our source, but the destination
    // class knows its schema already.
    $this->map = new MigrateSQLMap($this->machineName, array(
      'style' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'description' => 'Topic ID',
      ),
    ), MigrateDestinationTerm::getKeySchema());

    // Assign mappings TO destination fields FROM source fields. To discover
    // the names used in these calls, use the drush commands
    //  drush migrate-fields-destination BeerTerm
    //  drush migrate-fields-source BeerTerm
    // or review the detail pages in the UI.
    $this
      ->addFieldMapping('name', 'style');
    $this
      ->addFieldMapping('description', 'details');

    // Documenting your mappings makes it easier for the whole team to see
    // exactly what the status is when developing a migration process.
    $this
      ->addFieldMapping('parent_name', 'style_parent')
      ->description(t('The incoming style_parent field is the name of the term parent'));

    // Mappings are assigned issue groups, by which they are grouped on the
    // migration info page when the migrate_ui module is enabled. The default
    // is 'Done', indicating active mappings which need no attention. A
    // suggested practice is to use groups of:
    // Do Not Migrate (or DNM) to indicate source fields which are not being
    //  used, or destination fields not to be populated by migration.
    // Client Issues to indicate input from the client is needed to determine
    //  how a given field is to be migrated.
    // Implementor Issues to indicate that the client has provided all the
    //  necessary information, and now the implementor needs to complete the
    //  work.
    $this
      ->addFieldMapping(NULL, 'hoppiness')
      ->description(t('This info will not be maintained in Drupal'))
      ->issueGroup(t('DNM'));

    // Open mapping issues can be assigned priorities (the default is
    // MigrateFieldMapping::ISSUE_PRIORITY_OK). If you're using an issue
    // tracking system, and have defined issuePattern (see BasicExampleMigration
    // above), you can specify a ticket/issue number in the system on the
    // mapping and migrate_ui will link directly to it.
    $this
      ->addFieldMapping(NULL, 'region')
      ->description('Will a field be added to the vocabulary for this?')
      ->issueGroup(t('Client Issues'))
      ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
      ->issueNumber(770064);

    // It is good practice to account for all source and destination fields
    // explicitly - this makes sure that everyone understands exactly what is
    // being migrated and what is not. Also, migrate_ui highlights unmapped
    // fields, or mappings involving fields not in the source and destination,
    // so if (for example) a new field is added to the destination typ it's
    // immediately visible, and you can decide if anything needs to be
    // migrated into it.
    $this
      ->addFieldMapping('format')
      ->issueGroup(t('DNM'));
    $this
      ->addFieldMapping('weight')
      ->issueGroup(t('DNM'));
    $this
      ->addFieldMapping('parent')
      ->issueGroup(t('DNM'));

    // We conditionally DNM these fields, so your field mappings will be clean
    // whether or not you have path and/or pathauto enabled.
    $destination_fields = $this->destination
      ->fields();
    if (isset($destination_fields['path'])) {
      $this
        ->addFieldMapping('path')
        ->issueGroup(t('DNM'));
      if (isset($destination_fields['pathauto'])) {
        $this
          ->addFieldMapping('pathauto')
          ->issueGroup(t('DNM'));
      }
    }
  }

}

/**
 * And that's it for the BeerTerm migration! For a simple migration, all you
 * have to do is define the source, the destination, and mappings between the
 * two - to import the data you simply do:
 * drush migrate-import BeerTerm
 *
 * However, in real-world migrations not everything can be represented simply
 * through static mappings - you will frequently need to do some run-time
 * transformations of the data.
 */
class BeerUserMigration extends BasicExampleMigration {
  public function __construct($arguments) {

    // The basic setup is similar to BeerTermMigraiton
    parent::__construct($arguments);
    $this->description = t('Beer Drinkers of the world');
    $query = db_select('migrate_example_beer_account', 'mea')
      ->fields('mea', array(
      'aid',
      'status',
      'posted',
      'name',
      'nickname',
      'password',
      'mail',
      'sex',
      'beers',
    ));
    $this->source = new MigrateSourceSQL($query);
    $this->destination = new MigrateDestinationUser();
    $this->map = new MigrateSQLMap($this->machineName, array(
      'aid' => array(
        'type' => 'int',
        'not null' => TRUE,
        'description' => 'Account ID.',
      ),
    ), MigrateDestinationUser::getKeySchema());

    // One good way to organize your mappings in the constructor is in three
    // groups - mapped fields, unmapped source fields, and unmapped destination
    // fields.
    // Mapped fields
    // This is a shortcut you can use when the source and destination field
    // names are identical (e.g., the email address field is named 'mail' in
    // both the source table and in Drupal).
    $this
      ->addSimpleMappings(array(
      'status',
      'mail',
    ));

    // Our source table has two entries for 'alice', but we must have a unique
    // username in the Drupal 'users' table. dedupe() creates new, unique
    // destination values when the source field of that value already exists.
    // For example, if we're importing a user with name 'test' and a user
    // 'test' already exists in the target, we'll create a new user named
    // 'test_1'.
    // dedupe() takes the Drupal table and column for determining uniqueness.
    $this
      ->addFieldMapping('name', 'name')
      ->dedupe('users', 'name');

    // The migrate module automatically converts date/time strings to UNIX
    // timestamps.
    $this
      ->addFieldMapping('created', 'posted');
    $this
      ->addFieldMapping('pass', 'password');

    // Instead of mapping a source field to a destination field, you can
    // hardcode a default value. You can also use both together - if a default
    // value is provided in addition to a source field, the default value will
    // be applied to any rows where the source field is empty or NULL.
    $this
      ->addFieldMapping('roles')
      ->defaultValue(DRUPAL_AUTHENTICATED_RID);
    $this
      ->addFieldMapping('field_migrate_example_gender', 'sex');

    // The source field has beer names separated by a pipe character ('|'). By
    // adding ->separator('|'), the migration will automatically break them out,
    // look up the node with each title, and assign the node reference to this
    // user.
    if (module_exists('node_reference')) {
      $this
        ->addFieldMapping('field_migrate_example_favbeers', 'beers')
        ->separator('|');
    }
    else {
      $this
        ->addFieldMapping(NULL, 'beers')
        ->issueGroup(t('DNM'));
    }

    // Unmapped source fields
    $this
      ->addFieldMapping(NULL, 'nickname')
      ->issueGroup(t('DNM'));

    // Unmapped destination fields
    // This is a shortcut you can use to mark several destination fields as DNM
    // at once.
    $this
      ->addUnmigratedDestinations(array(
      'access',
      'data',
      'is_new',
      'language',
      'login',
      'picture',
      'role_names',
      'signature',
      'signature_format',
      'theme',
      'timezone',
    ));

    // Oops, we made a typo - this should have been 'init'! If you have
    // migrate_ui enabled, look at the BeerUser info page - you'll see that it
    // displays a warning "int used as destination field in mapping but not in
    // list of destination fields", and also lists "1 unmapped" under Destination,
    // where it highlights "init" as unmapped.
    $this
      ->addFieldMapping('int')
      ->issueGroup(t('DNM'));
    $destination_fields = $this->destination
      ->fields();
    if (isset($destination_fields['path'])) {
      $this
        ->addFieldMapping('path')
        ->issueGroup(t('DNM'));
      if (isset($destination_fields['pathauto'])) {
        $this
          ->addFieldMapping('pathauto')
          ->issueGroup(t('DNM'));
      }
    }
  }

}

/**
 * The BeerNodeMigration uses the migrate_example_beer_node table as source
 * and creates Drupal nodes of type 'Beer' as destination.
 */
class BeerNodeMigration extends BasicExampleMigration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $this->description = t('Beers of the world');

    // We have a more complicated query. The Migration class fundamentally
    // depends on taking a single source row and turning it into a single
    // Drupal object, so how do we deal with zero or more terms attached to
    // each node? One way (valid for MySQL only) is to pull them into a single
    // comma-separated list.
    $query = db_select('migrate_example_beer_node', 'b')
      ->fields('b', array(
      'bid',
      'name',
      'body',
      'excerpt',
      'aid',
      'countries',
      'image',
      'image_alt',
      'image_title',
      'image_description',
    ));
    $query
      ->leftJoin('migrate_example_beer_topic_node', 'tb', 'b.bid = tb.bid');

    // Gives a single comma-separated list of related terms
    $query
      ->groupBy('tb.bid');
    $query
      ->addExpression('GROUP_CONCAT(tb.style)', 'terms');

    // By default, MigrateSourceSQL derives a count query from the main query -
    // but we can override it if we know a simpler way
    $count_query = db_select('migrate_example_beer_node', 'b');
    $count_query
      ->addExpression('COUNT(bid)', 'cnt');

    // Passing the cache_counts option means the source count (shown in
    // drush migrate-status) will be cached - this can be very handy when
    // dealing with a slow source database.
    $this->source = new MigrateSourceSQL($query, array(), $count_query, array(
      'cache_counts' => TRUE,
    ));

    // Set up our destination - nodes of type migrate_example_beer
    $this->destination = new MigrateDestinationNode('migrate_example_beer');
    $this->map = new MigrateSQLMap($this->machineName, array(
      'bid' => array(
        'type' => 'int',
        'not null' => TRUE,
        'description' => 'Beer ID.',
        'alias' => 'b',
      ),
    ), MigrateDestinationNode::getKeySchema());

    // Mapped fields
    $this
      ->addFieldMapping('title', 'name')
      ->description(t('Mapping beer name in source to node title'));
    $this
      ->addFieldMapping('sticky')
      ->description(t('Should we default this to 0 or 1?'))
      ->issueGroup(t('Client questions'))
      ->issueNumber(765736)
      ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_LOW);

    // References to related objects (such as the author of the content) are
    // most likely going to be identifiers from the source data, not Drupal
    // identifiers (such as uids). You can use the mapping from the relevant
    // migration to translate from the old ID to the Drupal identifier.
    // Note that we also provide a default value of 1 - if the lookup fails to
    // find a corresponding uid for the aid, the owner will be the administrative
    // account.
    $this
      ->addFieldMapping('uid', 'aid')
      ->sourceMigration('BeerUser')
      ->defaultValue(1);

    // This is a multi-value text field - in the source data the values are
    // separated by |, so we tell migrate to split it by that character.
    $this
      ->addFieldMapping('field_migrate_example_country', 'countries')
      ->separator('|');

    // These are related terms, which by default will be looked up by name.
    $this
      ->addFieldMapping('migrate_example_beer_styles', 'terms')
      ->separator(',');

    // Some fields may have subfields such as text formats or summaries. These
    // can be individually mapped as we see here.
    $this
      ->addFieldMapping('body', 'body');
    $this
      ->addFieldMapping('body:summary', 'excerpt');

    // File fields are more complex - the file needs to be copied, a Drupal
    // file entity (file_managed table row) created, and the field populated.
    // There are several different options involved. It's usually best to do
    // migrate the files themselves in their own migration (see wine.inc for an
    // example), but they can also be brought over through the field mapping.
    // We map the filename (relative to the source_dir below) to the field
    // itself.
    $this
      ->addFieldMapping('field_migrate_example_image', 'image');

    // The file_class determines how the 'image' value is interpreted, and what
    // other options are available. In this case, MigrateFileUri indicates that
    // the 'image' value is a URI.
    $this
      ->addFieldMapping('field_migrate_example_image:file_class')
      ->defaultValue('MigrateFileUri');

    // Here we specify the directory containing the source files.
    $this
      ->addFieldMapping('field_migrate_example_image:source_dir')
      ->defaultValue(drupal_get_path('module', 'migrate_example'));

    // And we map the alt and title values in the database to those on the image.
    $this
      ->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
    $this
      ->addFieldMapping('field_migrate_example_image:title', 'image_title');

    // No description for images, only alt and title
    $this
      ->addUnmigratedSources(array(
      'image_description',
    ));

    // Unmapped destination fields
    // Some conventions we use here: with a long list of fields to ignore, we
    // arrange them alphabetically, one distinct field per line (although
    // subfields of the same field may be grouped on the same line), and indent
    // subfields to distinguish them from top-level fields.
    $this
      ->addUnmigratedDestinations(array(
      'body:format',
      'changed',
      'comment',
      'created',
      'field_migrate_example_image:destination_dir',
      'field_migrate_example_image:destination_file',
      'field_migrate_example_image:file_replace',
      'field_migrate_example_image:preserve_files',
      'field_migrate_example_image:urlencode',
      'is_new',
      'language',
      'log',
      'migrate_example_beer_styles:source_type',
      'migrate_example_beer_styles:create_term',
      'promote',
      'revision',
      'revision_uid',
      'status',
      'tnid',
    ));
    $destination_fields = $this->destination
      ->fields();
    if (isset($destination_fields['path'])) {
      $this
        ->addFieldMapping('path')
        ->issueGroup(t('DNM'));
      if (isset($destination_fields['pathauto'])) {
        $this
          ->addFieldMapping('pathauto')
          ->issueGroup(t('DNM'));
      }
    }
    if (module_exists('statistics')) {
      $this
        ->addUnmigratedDestinations(array(
        'totalcount',
        'daycount',
        'timestamp',
      ));
    }
  }

}

/**
 * Import items from the migrate_example_beer_comment table and make them into
 * Drupal comment objects.
 */
class BeerCommentMigration extends BasicExampleMigration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $this->description = 'Comments about beers';
    $query = db_select('migrate_example_beer_comment', 'mec')
      ->fields('mec', array(
      'cid',
      'cid_parent',
      'name',
      'mail',
      'aid',
      'body',
      'bid',
      'subject',
    ))
      ->orderBy('cid_parent', 'ASC');
    $this->source = new MigrateSourceSQL($query);

    // Note that the machine name passed for comment migrations is
    // 'comment_node_' followed by the machine name of the node type these
    // comments are attached to.
    $this->destination = new MigrateDestinationComment('comment_node_migrate_example_beer');
    $this->map = new MigrateSQLMap($this->machineName, array(
      'cid' => array(
        'type' => 'int',
        'not null' => TRUE,
      ),
    ), MigrateDestinationComment::getKeySchema());

    // Mapped fields
    $this
      ->addSimpleMappings(array(
      'name',
      'subject',
      'mail',
    ));
    $this
      ->addFieldMapping('status')
      ->defaultValue(COMMENT_PUBLISHED);
    $this
      ->addFieldMapping('nid', 'bid')
      ->sourceMigration('BeerNode');
    $this
      ->addFieldMapping('uid', 'aid')
      ->sourceMigration('BeerUser')
      ->defaultValue(0);
    $this
      ->addFieldMapping('pid', 'cid_parent')
      ->sourceMigration('BeerComment')
      ->description('Parent comment.');
    $this
      ->addFieldMapping('comment_body', 'body');

    // No unmapped source fields
    // Unmapped destination fields
    $this
      ->addUnmigratedDestinations(array(
      'changed',
      'comment_body:format',
      'created',
      'homepage',
      'hostname',
      'language',
      'thread',
    ));
    $destination_fields = $this->destination
      ->fields();
    if (isset($destination_fields['path'])) {
      $this
        ->addFieldMapping('path')
        ->issueGroup(t('DNM'));
      if (isset($destination_fields['pathauto'])) {
        $this
          ->addFieldMapping('pathauto')
          ->issueGroup(t('DNM'));
      }
    }
  }

}

Classes

Namesort descending Description
BasicExampleMigration To define a migration process from a set of source data to a particular kind of Drupal object (for example, a specific node type), you define a class derived from Migration. You must define a constructor to initialize your migration object.
BeerCommentMigration Import items from the migrate_example_beer_comment table and make them into Drupal comment objects.
BeerNodeMigration The BeerNodeMigration uses the migrate_example_beer_node table as source and creates Drupal nodes of type 'Beer' as destination.
BeerTermMigration There are four essential components to set up in your constructor: $this->source - An instance of a class derived from MigrateSource, this will feed data to the migration. $this->destination - An instance of a class derived from…
BeerUserMigration And that's it for the BeerTerm migration! For a simple migration, all you have to do is define the source, the destination, and mappings between the two - to import the data you simply do: drush migrate-import BeerTerm