Migration Season: Moving Your Stuff to Drupal 8

Migration Season: Moving Your Stuff to Drupal 8

Caleb Thorne

d.o/u/draenen
@calebthorne
www.calebthorne.com

Monarch Digital        Drupal Site Support

Migrate in Core

  • Migrate: Main migration stuff
  • Migrate Drupal: Migrate from D6 or D7
  • Migrate Drupal UI: The "Big Red Button"

Migrate Drupal UI

  1. Enable the modules
  2. Go to /upgrade
  3. Connect to source database
  4. Review mappings
  5. Click "Perform upgrade"

Experimental! Alpha stability as of 8.1

  • Missing upgrades paths in contrib
  • Bugs, including security or data integrity
  • No backwards compatibility
  • No upgrade path

Custom migrations

  • YAML configuration
  • Object-oriented concepts
  • Extend a Class
  • Implement an Interface
  • PHP Annotations

Migration Workflow

Source Process Destination
  • MySQL
  • CSV
  • HTML
  • get
  • callback
  • dedupe
  • static map
  • Node
  • User
  • Block
  • Config

Migrate in Contrib

  • Migrate Plus (https://www.drupal.org/project/migrate_plus)
    Migration config install, migration groups, examples
  • Migrate Tools (https://www.drupal.org/project/migrate_tools)
    Drush commands, UI in progress

Example: Pets

Migration Config

  • Custom module
  • Configuration entities (Migrate Plus)
  • Allow custom one-off migrations

Migration Group

migrate_pets/config/install/migrate_plus.migration_group.pets.yml

id: pets
label: Pet migrations
description: A few simple pet migrations.
source_type: Custom tables
source:
  key: migrate
    

$databases = array(
  'default' => array(
    'default' => array(...),
  ),
  'migrate' => array(
    'default' => array(
      'database' => 'pets',
      ...
    ),
  ),
);
    

Migration

migrate_pets/config/install/migrate_plus.migration.pets.yml

id: pets
label: "Friendly, Furry Pets"
migration_group: pets
source:
  plugin: pet_source
destination:
  plugin: "entity:node"
process:
  nid: pid
  title: name
  type:
    plugin: default_value
    default_value: pet
  uid:
    plugin: migration
    migration: pet_owners
    source: oid
  field_pet_type:
    plugin: cat_to_dog
  field_photo: picture
migration_dependencies:
  required:
    - pet_owners
dependencies:
  enforced:
    module:
      - migrate_pets
    

Source Plugin

migrate_plus.migration.pets.yml

source:
  plugin: pet_source
    

Source Plugin

  • Extend \Drupal\migrate\Plugin\migrate\source\SqlBase
  • Implement MigrateSourceInterface::fields()
  • Implement MigrateSourceInterface::getIds()
  • Implement SQLBase::query()
  • Implement MigrateSourceInterface::prepareRow()
@MigrateSource

use Drupal\migrate\Plugin\migrate\source\SqlBase

/**
 * Pet source from database.
 *
 * @MigrateSource(
 *   id = "pet_source"
 * )
 */
class Pet extends SQLBase {
    
MigrateSourceInterface::fields()

/**
 * {@inheritdoc}
 */
public function fields() {
  return [
    'pid' => $this->t('Pet id'),
    'oid' => $this->t('Owner id'),
    'type' => $this->t('Pet type'),
    'name' => $this->t('Pet name'),
    'picture' => $this->t('Pet photo'),
  ];
}
    
MigrateSourceInterface::getIds()

/**
 * {@inheritdoc}
 */
public function getIds() {
  return ['pid' => ['type' => 'integer']];
}
    
SQLBase::query()

/**
 * {@inheritdoc}
*/
public function query() {
  $query = $this->select('pet', 'p')
    ->fields('p', ['pid', 'oid', 'type', 'name', 'picture'])
    ->condition('picture', 'IS NOT NULL');
  return $query;
}
    

Remember this?

migrate_plus.migration_group.pets.yml

source:
  key: migrate
      
SQLBase::prepareRow()

/**
 * {@inheritdoc}
 */
public function prepareRow(Row $row) {
  if ($picture = $row->getSourceProperty('picture')) {
    $row->setSourceProperty('picture', $this->animate($picture));
  }
  return parent::prepareRow($row);
}
    

Destination Plugin

migrate_plus.migration.pets.yml

destination:
  plugin: "entity:node"
    

destination:
  plugin: pet_dest
    

Destination Plugin

  • Extend \Drupal\migrate\Plugin\migrate\destination\DestinationBase
  • Implement MigrateDestinationInterface::fields()
  • Implement MigrateDestinationInterface::getIds()
  • Implement MigrateDestinationInterface::import()
  • Implement MigrateDestinationInterface::rollback()
@MigrateDestination

use Drupal\migrate\Plugin\migrate\destination\DestinationBase

/**
 * Pet destination.
 *
 * @MigrateDestination(
 *   id = "pet_dest"
 * )
 */
class PetDestination extends DestinationBase {
    
MigrateDestinationInterface::fields()

/**
 * {@inheritdoc}
 */
public function fields(MigrationInterface $migration = NULL) {
  // TODO: Implement fields() method.
}
    
MigrateDestinationInterface::fields()

/**
 * {@inheritdoc}
 */
public function fields(MigrationInterface $migration = NULL) {
  return [
    'pid' => $this->t('Pet id'),
    'oid' => $this->t('Owner id'),
    'type' => $this->t('Pet type'),
    'name' => $this->t('Pet name'),
    'photo' => $this->t('Pet photo'),
  ];
}
    
MigrateDestinationInterface::getIds()

/**
 * {@inheritdoc}
 */
public function getIds() {
  return [
    'pid' => ['type' => 'integer']
  ];
}
    
MigrateDestinationInterface::import()

/**
 * {@inheritdoc}
 */
public function import(Row $row, $old_destination_id_values = []) {
  $pet = $this->save($row);
  if (!$pet) {
    throw new MigrateException('Could not save pet');
  }
}
    
MigrateDestinationInterface::rollback()

/**
 * {@inheritdoc}
 */
public function rollback(array $destination_identifier) {
  $pet = $this->load(reset($destination_identifier));
  if ($pet) {
    $pet->delete();
  }
}
    

Process Plugins

migrate_plus.migration.pets.yml

process:
  nid: pid
  title:
    plugin: callback
    callable: trim
    source: name
  type:
    plugin: default_value
    default_value: pet
  uid:
    plugin: migration
    migration: pet_owners
    source: oid
  field_pet_type:
    plugin: cat_to_dog
    source: type
  field_photo: picture
    

Process Pipeline


title:
  - plugin: callback
    callable: trim
    source: title
  - plugin: substr
    start: -1
    length: 10
    

Process Plugins

migrate_plus.migration.pets.yml

field_pet_type:
  plugin: cat_to_dog
  source: type
    

Process Plugin

  • Extend \Drupal\migrate\ProcessPluginBase
  • Implement MigrateProcessInterface::transform()
@MigrateProcessPlugin

use Drupal\migrate\ProcessPluginBase

/**
 * Turn cats into dogs.
 *
 * @MigrateProcessPlugin(
 *   id = "cat_to_dog",
 * )
 */
class CatToDog extends ProcessPluginBase {
    
MigrateProcessInterface::transform()

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

  return ($value == 'cat') ? 'dog' : $value;
}
    

Migration Dependencies

migrate_plus.migration.pets.yml

migration_dependencies:
  required:
    - pet_owner
      

Migration Dependencies

Watch out!

migrate_plus.migration.pets.yml

dependencies:
  enforced:
    module:
      - migrate_pets
    

Running migrations

migrate-status (ms)

$ drush ms
Group: pets   Status  Total  Imported  Unprocessed  Last imported
pet_owners    Idle    50     0         N/A
pets          Idle    1455   0         N/A
    

Running migrations

migrate-import (mi)

$ drush mi pets
Processed 1455 items (1455 created, 0 updated, 0 failed, 0 ignored) - done with 'pets'
    

Running migrations

migrate-rollback (mr)

$ drush mr pets
Rolled back 1455 items - done with 'pets'
    

Other commands

drush migrate-stop

drush migrate-reset-status

drush migrate-messages

Contribute

Further reading

Slides available at
www.calebthorne.com/presentation/drupal/drupal8-migrations