Skipping a Version - Migrating from Drupal 6 to Drupal 8 with Drupal Migrate
I recently had the opportunity to migrate content from a Drupal 6 site to a Drupal 8 site. This was especially interesting for me as I hadn’t used Drupal 6 before. As you’d expect, there are some major infrastructure changes between Drupal 6 and Drupal 8. Those differences introduce some migration challenges that I’d like to share.
The Migrate module is a wonderful thing. The vast majority of node-based content can be migrated into a Drupal 8 site with minimal effort, and for the content that doesn’t quite fit, there are custom migration sources. A custom migration source is a small class that can provide extra data to your migration in the form of source fields. Typically, a migration will map source fields to destination fields, expecting the fields to exist on both the source node type and destination node type. We actually published an in-depth, two-part blog series about how we use Drupal Migrate to populate Drupal sites with content in conjunction with Google Sheets in our own projects.
In the following example, we are migrating the value of content_field_text_author from Drupal 6 to field_author in Drupal 8. These two fields map one-to-one:
<span class="s">id</span><span class="pi">:</span> <span class="s">book</span><span class="s">label</span><span class="pi">:</span> <span class="s">Book</span><span class="s">migration_group</span><span class="pi">:</span> <span class="s">d6</span><span class="s">deriver</span><span class="pi">:</span> <span class="s">Drupal\node\Plugin\migrate\D6NodeDeriver</span><span class="s">source</span><span class="pi">:</span> <span class="s">key</span><span class="pi">:</span> <span class="s">migrate</span> <span class="s">target</span><span class="pi">:</span> <span class="s">d6</span> <span class="s">plugin</span><span class="pi">:</span> <span class="s">d6_node</span> <span class="s">node_type</span><span class="pi">:</span> <span class="s">book</span><span class="s">process</span><span class="pi">:</span> <span class="s">field_author</span><span class="pi">:</span> <span class="s">content_field_text_author</span><span class="s">destination</span><span class="pi">:</span> <span class="s">plugin</span><span class="pi">:</span> <span class="s">entity:node</span>
This field mapping works because content_field_text_author is a table in the Drupal 6 database and is recognized by the Migrate module as a field. Everyone is happy.
However, in Drupal 6, it’s possible for a field to exist only in the database table of the node type. These tables look like this:
<span class="n">mysql</span><span class="o">></span> <span class="k">DESC</span> <span class="n">content_type_book</span><span class="p">;</span><span class="o">+</span><span class="c1">----------------------------+------------------+------+-----+---------+-------+</span><span class="o">|</span> <span class="n">Field</span> <span class="o">|</span> <span class="k">Type</span> <span class="o">|</span> <span class="k">Null</span> <span class="o">|</span> <span class="k">Key</span> <span class="o">|</span> <span class="k">Default</span> <span class="o">|</span> <span class="n">Extra</span> <span class="o">|</span><span class="o">+</span><span class="c1">----------------------------+------------------+------+-----+---------+-------+</span><span class="o">|</span> <span class="n">vid</span> <span class="o">|</span> <span class="n">int</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">unsigned</span> <span class="o">|</span> <span class="k">NO</span> <span class="o">|</span> <span class="n">PRI</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span> <span class="o">|</span><span class="o">|</span> <span class="n">nid</span> <span class="o">|</span> <span class="n">int</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">unsigned</span> <span class="o">|</span> <span class="k">NO</span> <span class="o">|</span> <span class="n">MUL</span> <span class="o">|</span> <span class="mi">0</span> <span class="o">|</span> <span class="o">|</span><span class="o">|</span> <span class="n">field_text_issue_value</span> <span class="o">|</span> <span class="n">longtext</span> <span class="o">|</span> <span class="n">YES</span> <span class="o">|</span> <span class="o">|</span> <span class="k">NULL</span> <span class="o">|</span> <span class="o">|</span><span class="o">+</span><span class="c1">----------------------------+------------------+------+-----+---------+-------+</span>
If we want to migrate the content of field_text_issue_value to Drupal 8, we need to use a custom migration source.
Custom migration sources are PHP classes that live in the src/Plugin/migrate/source directory of your module. For example, you may have a PHP file located at src/Plugin/migrate/source/BookNode.php that would provide custom source fields for a Book content type.
A simple source looks like this:
namespace Drupal\custom_migrate_d6\Plugin\migrate\source;use Drupal\node\Plugin\migrate\source\d6\Node;/** * @MigrateSource( * id = "d6_book_node", * ) */class BookNode extends Node { /** * @inheritdoc */ public function query() { $query = parent::query(); $query->join('content_type_book', 'book', 'n.nid = book.nid'); $query->addField('book', 'field_text_issue_value'); return $query; }}
As you can see, we are using our migration source to modify the query the Migrate module uses to retrieve the data to be migrated. Our modification extracts the field_text_issue_value column of the book content type table and provides it to the migration as a source field.
To use this migration source, we need to make one minor change to change to our migration. We replace this:
plugin: d6_node
With this:
plugin: d6_book_node
We do this because our migration source extends the standard Drupal 6 node migration source in order to add our custom source field.
The migration now contains two source fields and looks like this:
<span class="s">id</span><span class="pi">:</span> <span class="s">book</span><span class="s">label</span><span class="pi">:</span> <span class="s">Book</span><span class="s">migration_group</span><span class="pi">:</span> <span class="s">d6</span><span class="s">deriver</span><span class="pi">:</span> <span class="s">Drupal\node\Plugin\migrate\D6NodeDeriver</span><span class="s">source</span><span class="pi">:</span> <span class="s">key</span><span class="pi">:</span> <span class="s">migrate</span> <span class="s">target</span><span class="pi">:</span> <span class="s">d6</span> <span class="s">plugin</span><span class="pi">:</span> <span class="s">d6_book_node</span> <span class="s">node_type</span><span class="pi">:</span> <span class="s">book</span><span class="s">process</span><span class="pi">:</span> <span class="s">field_author</span><span class="pi">:</span> <span class="s">content_field_text_author</span> <span class="s">field_issue</span><span class="pi">:</span> <span class="s">field_text_issue_value</span><span class="s">destination</span><span class="pi">:</span> <span class="s">plugin</span><span class="pi">:</span> <span class="s">entity:node</span>
You’ll find you can do a lot with custom migration sources, and this is especially useful with legacy versions of Drupal where you’ll have to fudge data at least a little bit. So if the Migrate module isn’t doing it for you, you’ll always have the option to step in and give it a little push.