Simple Drupal 6/7 to Drupal 8 Migrations

http://bit.ly/2aUhSXr

Christopher Bloom (illepic)

Scenario:

Legacy Drupal 6 site needs a migration to new Drupal 8 site. We want to migrate settings, content types, fields, users, files, content. The Drupal 6 site database is accessible from the freshly installed Drupal 8 database.

Caveats & Considerations

  • Not "in-place" upgrade like Drupal 6 to 7

  • No UI, command line only

  • Lots of contrib
  • Migrations changed from config to "plugins" in Drupal 8.1. If you're starting fresh in 8.1, no worries.
  • We can migrate configuration as well as content in Drupal 8

Modules + Tools Required

  • A Migration Group is a collection of Migrations
  • A Migration is generally one YAML file.
  • A Migration contains
    • a source plugin: "read rows from source (D6)"
    • a processing pipeline: "transform raw source values into what the destination needs"
    • a destination plugin: "D8 understands what this thing becomes (nodes, users, entities)"
  • ​Processing plugins are simple PHP files to help massage data from source to destination

Looks like this all together as a module

A single Migration YAML file might look like this:

status: true
dependencies: {  }
id: upgrade_d6_node_page
migration_tags:
  - 'Drupal 6'
migration_group: migrate_drupal_6
label: 'Nodes (page)'
source:
  plugin: d6_node
  node_type: page
process:
  nid: nid
  vid: vid
  type:
    plugin: default_value
    default_value: post
  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: basic_html
  body/value:
    plugin: remove_strings
    source: body
  field_media_primary:
    -
      source: nid
      plugin: img_assist
      type: 'mid'
    -
      plugin: iterator
      process:
        target_id: fid

  field_attachments:
    -
      plugin: iterator
      process:
        target_id: fid
        display:
          plugin: default_value
          default_value: 1
  field_group:
    source: nid
    plugin: og_lookup
destination:
  plugin: 'entity:node'
migration_dependencies:
  required:
    - upgrade_d6_user
    - upgrade_d6_node_type
    - upgrade_d6_file_media
  optional: { }

Tell D8 About Your D6 Database

In Drupal 8 settings.php, right next to your regular database configuration, add a db config FOR THE D6 SITE:

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

You now have configuration for

$databases['my_d8_site']['default']
and $databases['migrate']['default']

This EXACT name for the D6 db config is extremely important:

$databases['migrate']['default']

Aight, D8. Generate a Migration

 

From the D8 site root, run:

drush migrate-upgrade --configure-only

Boom. D8 just took its best guess at figuring out all the fields, nodes, files, etc. (hopefully)

Now let's dump all this out as YAML files:

drush config-export --destination=/tmp/migrate

Many YAML files will now appear in /tmp/migrate. We're going to move all these to a custom module shortly.

Let's make a module so Drupal understands how to find this config

 

Use Drupal Console to generate a module for us

drupal generate:module

A series of questions will appear. We'll call the module custom_migration. Use the default options provided except:

  • Would you like to add module dependencies: yes
  • Module dependencies separated by commas: 
    migrate_drupal, migrate_plus

 

Create a folder in your new module:

custom_migration/config/install

Move our previously generated YAML to the new module:

cp /tmp/migrate/migrate_plus.migration.* \
    /tmp/migrate/migrate_plus.migration_group.migrate_*.yml \
    /path/to/your/module/config/install/

Just make sure you do NOT include the default config group file called migrate_plus.migration_group.default.yml

Let's run it

Running drush migrate-status shows us the individual migrations D8 knows about

$ drush migrate-status
 Group: migrate_drupal_6              Status  Total  Imported  Unprocessed  Last imported       
 upgrade_d6_system_site               Idle    1      1         0             
 upgrade_d6_url_alias                 Idle    6972   0         6972                             
 upgrade_d6_file                      Idle    9914   3499      0             
 upgrade_d6_user_picture_file         Idle    281    281       0             
 upgrade_user_picture_entity_display  Idle    1      1         0             
 upgrade_d6_user                      Idle    1402   1133      0             
 upgrade_d6_file_media                Idle    9914   3495      0             
 upgrade_d6_book_blog                 Idle    822    0         822                              
 upgrade_d6_node_event                Idle    1351   0         1351                             
 upgrade_d6_node_forum                Idle    2305   0         2305                             
 upgrade_d6_node_guild_app            Idle    679    677       0             
 upgrade_d6_node_image                Idle    2243   0         2243                             
 upgrade_d6_node_page                 Idle    9      0         9                                

Run all migrations:

drush migrate-import --all

Congrats!

Next up:

Modify incoming data with built-in process plugins

 

 

source:
  plugin: d6_file
process:
  fid: fid
  # Skip any filename in the map below
  filename:
    -
      plugin: static_map
      source: filename
      bypass: true
      map:
        'gallery grid': ''
        preview: ''
        thumbnail: ''
        img_assist_properties: ''
        large: ''
    -
      plugin: skip_contains
      skip:
        - 'furiousraid'
    -
      plugin: skip_on_empty
      method: row
    -
      plugin: callback
      callable: basename
      source: filepath

Next up: Write your own process plugins
(in custom_migration/src/Plugin/migrate/process/ImagePath.php)

<?php

/**
 * @file
 * Contains \Drupal\df_migration\Plugin\migrate\process\ImagePath.
 */

namespace Drupal\df_migration\Plugin\migrate\process;

use Drupal\migrate\Annotation\MigrateProcessPlugin;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;

/**
 * This plugin fixes d6 Image file names to be the actual filename from filesystem
 *
 * @MigrateProcessPlugin(
 *   id = "image_path"
 * )
 */
class ImagePath extends ProcessPluginBase {

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, 
                            $destination_property) {
    return basename($value);
  }
}

Next up: Edit a migration YAML file, reload it in Drupal, and rerun a migration

# Rollback migration
drush mr upgrade_d6_node_page

# (Edit the migrate_plus.migration.upgrade_d6_node_page.yml file)

# Now, hard reload the file using Config Development module
drush cdi1 modules/custom/df_migration/config/install/migrate_plus.migration.upgrade_d6_node_page.yml \
    && drupal cr all

# Re-run this migration
drush mi upgrade_d6_node_guild_app

Next up: unbork a crashed migration

drush php-eval 'var_dump(Drupal::keyValue("migrate_status")->set('your_migration_name', 0))'

Super Helpful Resources

Drupal 8 Migrations

By Christopher Bloom

Drupal 8 Migrations

  • 3,921