Custom Drupal-to-Drupal migration

Introduction

Sooner or later every developer faces this scary (actually, not) process called "Migration". If you’re one of them, you must have heard about one of the most outstanding and solid modules for Drupal 7: Migrate. Actually, this is not even the module, it’s a migration framework that provides a wonderful API and a variety of powerful tools for data migration into Drupal from various sources. From my point of view, the existence of such modules is very important for the Drupal community and the Drupal ecosystem, because it provides an easy way to move projects from another CMS or from any other frameworks to Drupal. Thus we can offer our client a relatively cheap way to change their system in favor of Drupal. In this article, you’ll read how to use Migrate API in Drupal 8 and we’ll build a simple module to execute the migration from Drupal 7 to Drupal 8.

A bit of theory

A migration process can be presented in this diagram.

A migration process
A migration process

This is kind of a common approach to migration, there is no any specific Drupal aspects. We have a source database (which is not necessary should be a database, it can be CSV, XML,  etc.) and we have a destination database which is a Drupal 8 database of course. As you can see on the diagram between these two databases (source and destination) we have a list of ordered operations.

The first one is a getting of data, it’s a query system which lets you get the data from the source database. Then goes a mapping, where you can set which field from the source database should go in what source in the destination database. The next step is processing, in this operation, we can change the data taken from the source DB. For example, we need to change a format of the taken data to fit new structures in Drupal 8. The last one is a setting, it’s a part of the destination object that sets the data in the structures of the destination database (Drupal 8 database structure).

The Migration API in Drupal 8

Let’s look how the things are going in Drupal 8 and with the Migration module. We don’t have a Drupal 8 release of the Migrate project on drupal.org because the most of its features were ported into Drupal core: the main migration module “migrate” and the module for D6 and D7 migrations “migrate_drupal”. I told that not all the features of the Migrate module were ported into the core, but it doesn’t mean that we’re bounded by this situation in comparison with Drupal 7 version, not at all. All these familiar features exist in the contrib project. Here is a list of the modules providing features for Drupal 8 that Drupal 7 module has:

  1. Migrate Tools: this module provides general-purpose Drush commands and a basic UI for managing migrations.
  2. Migrate Upgrade: here we can find Drush commands for Drupal-to-Drupal migrations.
  3. Migrate Plus: a very helpful module. Provides API extensions, for example, PREPARE_ROW event, an additional source and destination plugins, a well-documented example module and a groups feature.
  4. Migrate Source CSVMigrate Spreadsheet: a couple of helpful modules that provide specific source plugins.

Logically the structure of the migration API does not differ much in Drupal 7: for example, we have sources, then we process data taken from these sources and after that, all data goes to a destination. In the 8th version, we have 3 main parts which are implemented via a plugin system: source, process, and destination.

By default Drupal 8 includes three migration modules (migrate, migrate_drupal, migrate_drupal_ui) that can help you to do a simple upgrade from the 6th or the 7th versions. Also, it requires a clean & empty installation of Drupal 8. I have to admit that core’s upgrade works very decent for simple sites. But this is not our case.

Let’s imagine that we have to do the migration of content from one content type in Drupal 7 “Blog” to another content type in Drupal 8 - also “Blog”, into an existing site. Our first migration will be a straightforward one, so there won’t any complex actions, so data will be migrated as it is.

How to develop a migration module for Drupal 7-to-Drupal 8 migration

Let’s start creating our first migration module. At first, we need to establish a database connection to a D7 database, here is an example of the settings.php:

// Our default connection to Drupal 8.
$databases['default']['default'] = array (
  'database' => 'drupal8',
  'username' => 'root',
  'password' => 'root',
  'prefix' => '',
  'host' => 'localhost',
  'port' => '',
  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
  'driver' => 'mysql',
);
// Legacy database connection to Drupal 7.

$databases['old_drupal']['default'] = array (
  'database' => 'drupal7',
  'username' => 'root',
  'password' => 'root',
  'prefix' => '',
  'host' => 'localhost',
  'port' => '3306',
  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
  'driver' => 'mysql',
);

Once the database connection to an old site is established we can enable necessary contrib modules and start:

drush dl migrate_plus, migrate_tools

drush en migrate_plus, migrate_tools

Like any other Drupal 8 module, we need to add yml.info file, nothing special.

my_module.info.yml:
name: My Migrate
description: Performs an example of a simple Drupal-to-Drupal migration.
package: Other
dependencies:
 - migrate
 - migrate_drupal
 - migrate_plus
type: module
core: 8.x

As the next step, we need to define our mappings. Unlike D7, in D8 mappings are configuration entities defined in .yml configuration files. In comparison with D7, it is a good replacement for construction classes. We will create 2 configuration files: config for a migration group and migration itself. All config files should be placed in the following directory ‘config/install’ so that config entities are created at the same time as the module is installed.

Definition of a migration group

A name of the configuration file of a migration group will be the following: “migrate_plus.migration_group.mymig.yml”. I want to notice that migration groups in D8 are provided by the contrib module “migrate_plus”, so all our config names will be started with “migrate_plus”. The “migrate_plus” says that it’s a group config, and “mymig” it’s an id. Here are contents of the file with comments:

#id of the config
id: mymig
label: My Migration Group
description: Drupal 7 migrations group.
source_type: Drupal 7.
# In this section we set a database connection defined in settings.php
# So all migrations in this group will use this source.
shared_configuration:
  source:
    key: old_drupal
# Here we set a dependency on the module itself.
# This is necessary setting that deletes configs from a database 
# on uninstallation of the module.
dependencies:
  enforced:
    module:
      - my_migrate

Once this config will be installed and imported to the database we will see a new migration group on this page ‘admin/structure/migrate’.

Definition of migration and mapping

As I said migration is also a config file. Here is a first part of the yml "migrate_plus.migration.blog_mymig.yml" file where the basic settings such as id, migration group and others are defined:

id: mymig_blog
label: Blog
migration_tags:
  - Drupal 7
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
# Here we set an id of the migration group that we previously created to set up a correct source.
migration_group: mymig

In the following part, we’ll set a source and a destination. A plugin for source migration is d7_node that is provided by Drupal core. Also, we set a content type that we need to migrate from D7, in our case, it's “blog”. A destination plugin will be “entity:node”:

source:
  plugin: d7_node
  node_type: blog
destination:
  plugin: entity:node

When all basic settings like the source and the destination are set we can do a mapping (define process). This is a simple mapping for migration of base node properties. As you can notice, it’s quite simple to read and understand. (Of course, a “blog” content type should be created in D8):

process:
  type:
    plugin: default_value
    default_value: blog
  uid:
    plugin: default_value
    default_value: 1
  langcode:
    plugin: default_value
    source: language
    default_value: "und"
  title: title
  uid: node_uid
  status: status
  created: created
  changed: changed
  promote: promote
  sticky: sticky
  'body/format':
    plugin: default_value
    default_value: full_html
  'body/value': body
  revision_uid: revision_uid
  revision_log: log
  revision_timestamp: timestamp

That’s it with a coding part and now we can install our module and migrate blog posts to the D8 site. A status of migration will be available for checking here: admin/structure/migrate/manage/mymig/migrations. I usually use Drush command for running migration processes:

drush migrate-import mymig_blog
or
drush mi mymig_blog

Troubleshoot

Sometimes you can see the following message after completing a migration process: 0 processed (0 created, etc). This message can confuse you, but there are quick explanation and solution. It happens because your migration was executed once and you missed it, so you just need to rollback migration with the following command:

drush migrate-rollback migration_id

Conclusion

Now we have the understanding how Migration API works in Drupal 8. You’ve seen that not having a release for the Migrate module in Drupal 8 is not such a big deal. First of all, some module’s features were put into Drupal core while the others are available in the contrib project.

Also, we have an example of how to build a migration module. In the further articles, we’ll take a look deeper and will learn how to build complex custom migration.

If you need any help with the migration of your website - let us know

You might also like