Migrating within Drupal

Drupal is known for its flexibility and ability to be modified at nearly every step. However, it can be difficult to make changes to the structure of your website once you've created real content.

Recently, I needed to move some existing content from one Content Type to another. With all its fields, pre-existing nodes, and small differences between field names and configuration — this seemed like a daunting task.

After some research, I learned the task wasn't as difficult as it first seemed. The heavy lifting has already been done by contributors in the community. Using the Migrate module along with the useful utility framework Drupal-to-Drupal data migration (D2D) most of the hard work of writing a migration is complete.

The Migrate module is typically used for moving content from an old data source into Drupal. It provides several classes which can be extended for your own needs, whether that is importing from a CSV or another database. To write migrations for all your content, you extend a base class for each type that you're importing into.

That's where the D2D module is tremendously useful. It provides ready to use classes for Drupal specific migrations — such as moving content from a Drupal 6 site into Drupal 7.

In my case, I needed to move content from an Article content type into a new content type called News. To extend D2D I created a new custom module, for this post I'll call it example_d2d.

I created the following files in the example_d2d folder:

  • example_d2d.info
  • example_d2d.module
  • example_d2d.migrate.inc
  • node.inc

The .info file just contains the basic description of your module and lists the dependencies and included files.

name = "Example D2D migration"
description = "Migrate content to other types"
package = "Aten"
core = 7.x
 
dependencies[] = migrate
dependencies[] = migrate_d2d
 
files[] = example_d2d.migrate.inc
files[] = node.inc

The .module file is required by Drupal, but not necessary to run a migration as the code will be in the other two files. Leave it empty.

Declare your migration using the example_d2d.migrate.inc file.

<?php
 
/**
 * @file example_d2d.migrate.inc
 */
 
/**
 * Implements hook_migrate_api().
 */
function example_d2d_migrate_api() {
  /**
   * Declare the api version and migration group.
   */
  $api = array(
    'api' => 2,
    'groups' => array(
      'content_migration' => array(
        'title' => t('Migrate content within Drupal'),
      ),
      'migrations' => array(),
    ),
  );
 
  $common_arguments = array(
    'source_connection' => 'default',
    'source_version' => 7,
    'group_name' => 'content_migration',
  );
 
  $node_arguments = array(
    'news' => array(
      'class_name' => 'NewsNodeMigration',
      'description' => t('Migration of article to news'),
      'source_type' => 'article',
      'destination_type' => 'news',
    ),
  );
 
  foreach ($node_arguments as $migration_name => $arguments) {
    $arguments = array_merge_recursive($arguments, $common_arguments);
    $api['migrations'][$migration_name] = $arguments;
  }
 
  return $api;
}

Most of this code is just naming your migration and giving it a description, of note is 'source_connection' => 'default', which points Drupal to its own, default database as the source of this migration.

Also, notice the 'source_type' => 'article', and 'destination_type' => 'news', which set the source type and destination type.

This is a migration from node to node, so your class will be in the node.inc file and extend the D2D DrupalNode7Migration migration. Each of the fields has its own properties, the "body" field has a summary and a text format, each of these may be defined here. The content has a term reference, for "source_type" you can define "tid". Finally, an entity reference field can simply be added with no additional information.

<?php
 
class NewsNodeMigration extends DrupalNode7Migration {
  public function __construct(array $arguments) {
 
    parent::__construct($arguments);
 
    $this->addFieldMapping('field_news_content', 'body');
    $this->addFieldMapping('field_news_content:summary', 'body:summary');
    $this->addFieldMapping('field_news_content:format', 'body:format')
      ->defaultValue('full_html')
      ->callbacks(array($this, 'mapFormat'));
 
    $this->addFieldMapping('field_news_topic', 'field_topic');
    $this->addFieldMapping('field_news_topic:source_type')
    ->defaultValue('tid');
 
    $this->addFieldMapping('field_related_resource', 'field_related_resource');
  }
}

The migrate_ui module will let you browse available migrations, and will show fields that have been mapped, as well as fields which it does not detect in your migration. This is a useful way to learn what options you have available and what you might be missing.

D2D migrate UI Source
D2D migrate UI Mapping

Moving Drupal content from one type to another without losing data can be a challenge, but by writing a Migration in code you have the advantage of a process that is repeatable and easily rolled back for revisions or fixes. Multiple migrations can be performed or added to later, done in stages, or with dependencies. The classes it provides allow you to accomplish a complicated re-structuring with very little of your own custom code.

One of the obstacles to getting content in a Drupal site early is the need to establish the architecture of content types and fields. Moving content after it was created has traditionally been a time-consuming and error-prone chore. Existing sites can be hard to change, and new sites may rely on placeholder content for too long. Knowing that the tools exist to move content into new structures should free up UX and development teams to make improvements and revisions — and not get stuck with bad decisions.

Code Content Drupal