Let’s talk about Migrate, Drupal’s module
Let me start by saying that this is the first in a series of articles about my experience with Migrate.
If you want, you can go further by looking at this video from the Drupal Dev Days 2023.
Have you already used Migrate for Drupal? No?
Then first, let’s talk about some basics to start understanding this module!
What’s the purpose?
The migrate module provides a flexible framework for migrating content into Drupal from other sources.
drupal.org
This means that it will help us transform some kind of data source into Drupal content.
But how does our source get transformed into Drupal contents?
Let’s look at an example. A simple one, to understand how the process works. Let’s say I want to migrate my Drupal 7 into a new Drupal 10.
I need to transform my node basic page that contains one custom field for the node’s description on Drupal 7 into a basic page containing a custom field for the description too.
First, we’ll have our plugin sources communicating with the data source. The plugin source is going to query (in a way or another) the data source.
This means that we need to find a way to query our Drupal 7 to obtain all the nodes.
The Node module already proposes a plugin source for this:
/**
* @MigrateSource(
* id = "d7_node",
* source_module = "node"
* )
*/
class Node extends FieldableEntity {
public function query() {
$query = $this->select('node_revision', 'nr')
->fields('n', ['nid', 'type', ...])
->fields('nr', ['vid', 'title', …]);
...
$query->innerJoin('node', 'n', static::JOIN);
...
return $query;
}
So here, it’s going to select anything needed from node_revision and from node.
Now that we know which source plugin to use, let’s use it:
id: node_page_to_node_page
label: 'My simple from D7 node page to D10 node page'
status: true
migration_tags:
- all
- basic_page
- node
source:
plugin: d7_node
Here I started describing my migration file.
First, I write down everything that helps me to identify it like id, label and migration_tags.
Then, I’ll go ahead and specify that we’re going to be using Drupal’s Node plugin source.
Now that the module knows how we’re going to retrieve our base nodes, let’s prepare each line necessary.
But what do you mean by preparing? Aren’t the rows already prepared when executing the query?
Well, sort of.
Each property on your row is going to be named the way you named it in the query (or the alias if you used one).
But you can rename those properties, work with them, or create new ones if you want to.
/**
* @MigrateSource(
* id = "d7_node",
* source_module = "node"
* )
*/
class Node extends FieldableEntity {
public function prepareRow(Row $row): bool {
$nid = $row->getSourceProperty('nid');
...
if ($this->moduleExists('title')) {
$title_field = $row->getSourceProperty('title_field');
if (isset($title_field[0]['value'])) {
$row->setSourceProperty('title', $title_field[0]['value']);
}
}
return parent::prepareRow($row);
}
For example, node is going to create a new property title where the title of the node is going to be put in.
Thanks to this possibility, we can rename and process any data before to put it inside the Drupal contents.
We have fetched every data and prepared everything we needed. But we haven’t yet mapped the fields from D7 to the new fields on D10!
Here is where the process part comes in. Now we can map all the D7 fields into our new D10 fields.
process:
title: title
field_description/0/value: description
field_description/0/format:
- plugin: default_value
default_value: 'basic_html'
We can specify how our fields will be mapped from D7 into our new D10.
And now we can use the title property prepared by node and our own property description into our new fields.
Something is hidden here, we are using the get process plugin for title and description:
process:
title:
- plugin: get
source: title
field_description/0/value:
- plugin: get
source: description
You may find more information about it in the documentation.
We’re almost finished!
We have fetched all the necessary rows, prepared specific properties where necessary and mapped the fields.
But, how do we specify the kind of Drupal content we want to create?
We just need to use another type of plugin, a destination plugin.
destination:
plugin: 'entity:node'
default_bundle: 'page’
We are going to use one from core this time, and specify which bundle we are going to create.
In the end, all together, our migration file is going to look something like this:
id: node_page_to_node_page
label: 'My simple from D7 node page to D10 node page'
status: true
migration_tags:
- all
- basic_page
- node
source:
plugin: d7_node
process:
title: title
field_description/0/value: description
field_description/0/format:
- plugin: default_value
default_value: 'basic_html'
destination:
plugin: 'entity:node'
default_bundle: 'page’
And voilà! You now have a big picture view of how it all works!
As you can see, the different parts allow us to deliver some pretty powerful features. For simple cases, almost everything already exists, but for more advanced cases, we have everything at our disposal to deal with them !