Skip to main content
ocean waves

Blog Post

Registering Migrations in Drupal 8

How it’s done in Drupal 7

In Drupal 7, registering new migrations was fairly simple, and could be done a couple of ways. I'm going to reference the more common method, which was defining the migrations and groups with hook_migrate_api().

function example_migrate_api() {
  $api = array(
    'api' => 2,
      'groups' => array(
       'example' => array(
       'title' => t('Example'),
         'default_format' => 'filtered_html',
       ),
      ),
      'migrations' => array(
        'ExampleUser' => array(
          'class_name' => 'ExampleUserMigration',
    	'group_name' => 'example',
          ),
          'ExampleArticle' => array(
    	 'class_name' => 'ExampleArticleMigration',
             'group_name' => 'example',
          ),
       ),
   );
 return $api;
}

Once defined, all that needed to be done is to register new migrations with drush cache-clear all && drush migrate-register.

How it’s done in Drupal 8

In Drupal 8, registering migrations is done slightly different. Instead of using hook_migrate_api(), they are listed in yaml files and implemented as configuration for the module in module_root/config/install/*.yml

migrate_plus.migration.beer_node.yml: (Taken from migrate_example in migrate_plus http://cgit.drupalcode.org/migrate_plus/tree/migrate_example/config/install/migrate_plus.migration.beer_node.yml)

id: beer_node
label: Beers of the world
migration_group: beer
source:
  plugin: beer_node
destination:
  plugin: entity:node
process:
  type:
	plugin: default_value
	default_value: migrate_example_beer
  title: name
  nid: bid
  uid:
	plugin: migration
	migration: beer_user
	source: aid
  sticky:
	plugin: default_value
	default_value: 0
  field_migrate_example_country: countries
  field_migrate_example_beer_style:
	plugin: migration
	migration: beer_term
	source: terms
  'body/value': body
  'body/summary': excerpt
migration_dependencies:
  required:
	- beer_term
	- beer_user

The install configuration functionality of custom Drupal 8 modules is great, but using that for registering migrations currently has a major downside. The yaml files need to be in our module's config directory and the module can then be enabled. Once enabled our migrations will already be registered.

Why this doesn’t work as well as it should

At first this seems convenient until you have to register new migrations. Since migrations are registered on install, new migrations added should be able to be registered by uninstalling and re-enabling our custom module. drush pm-uninstall my_module -y && drush en my_module -y

When I ran the command to enable the custom module again I was expecting to be greeted with newly registered migrations, but instead was met with an error message complaining about the previous migration's configs already existing in the database.

exception 'Drupal\Core\Config\PreExistingConfigException' with message 'Configuration objects (migrate_plus.migration.beer_node) provided by my_module already exist in active configuration' in /var/www/html/my_module/src/web/core/lib/Drupal/Core/Config/PreExistingConfigException.php:65

How we can fix this

So to solve the problem of the existing configs not being removed on uninstallation of the custom module we need to implement hook_uninstall() in a new file: my_module.install

function my_module_uninstall() {
  // Get the path for the module we want to remove configs for.
  $module_path = DRUPAL_ROOT. base_path() . drupal_get_path('module', 'my_module') . '/config/install';

  // Get all of the migration config names.
  $configs = array();
  $files = file_scan_directory($module_path, '/migrate_plus/');

  if ($files) {
	foreach ($files as $file) {
  		$configs[] = $file->name;
	}

	// Delete each config using configFactory.
	foreach ($configs as $config_name) {
  		drush_print("..." . $config_name);
  		\Drupal::configFactory()->getEditable($config_name)->delete();
	}

	return TRUE;
  }
}

All we are doing here is checking our modules' configuration file names for the configuration names. Then, using Drupal's configFactory we delete the configs programmatically.

Now we can uninstall and re-enable our modules to register our migrations. But there's one other problem that should be addressed; uninstalling and re-enabling modules every time we have a new migration to be registered is annoying.

To solve this we're going to add some Drush commands to our custom module to do the registration without uninstalling the module.

First we need to define our drush commands. We're going to create 3 of them; One to delete existing configs, another to re-register migrations after they've been removed, and a third that does both so we only have to run one command. To define the commands we need to implement hook_drush_command() in my_module.drush.inc. 

/**
 * Implements hook_drush_command().
 */
function my_module_drush_command() {
  $items['migrate-prune-configs'] = array(
	'description' => dt('Prune migrate configs.'),
	'arguments' => [
  		'module_name' => dt('Module name'),
	],
	'aliases' => array('mpc'),
	'examples' => array(
  	'drush migrate-prune-configs module_name' => 'Removes migrate configurations.',
	),
	'callback' => 'my_module_migrate_prune_configs',
  );

  $items['moduler-config-reset'] = array(
	'description' => dt('Resets default config for a given module.'),
	'arguments' => [
  	'module_name' => dt('Module name'),
	],
	'aliases' => ['mcr'],
	'required-arguments' => 1,
	'callback' => 'my_module_config_reset',
  );

  $items['migrate-re-register-migrations'] = array(
	'description' => dt('Re-register migrations and groups.'),
	'arguments' => [
  	'module_name' => dt('Module name'),
	],
	'aliases' => ['mrm'],
	'required-arguments' => 1,
	'callback' => 'my_module_re_register_migrations',
  );

  return $items;
}

Now that we have the commands defined we can add the callbacks that do the work for us.

/**
 * Removes configs for a given module.
 */
function my_module_migrate_prune_configs($module_name) {
  //Confirmation will be Y/N when use type “y” the condition will be executed if not, it will not.
  if (drush_confirm(dt("Are you sure you want to remove migrate configs?"))) {
	drush_print("Removing.....");
   
	if (_my_module_remove_configs($module_name)) {
  	drush_print("Removed successfully");

  	return TRUE;
	}
  }
}

/**
 * Installs configs for a given module.
 * @param  string $module_name module name.
 */
function my_module_config_reset($module_name) {
  if (!in_array($module_name, array_keys(\Drupal::moduleHandler()->getModuleList()))) {
	return drush_set_error(dt('Module @module_name not found.', ['@module_name' => $module_name]));
  }
  // Initiate config installer for given module.
  if (_my_module_install_config($module_name)) {
	drush_print('Configuration was successfully reset.');

	return TRUE;
  }
}

/**
 * Drush command callback for re-registering migration configs.
 * @param  string $module_name module name.
 */
function my_module_re_register_migrations($module_name) {
  // Check input.
  if (!in_array($module_name, array_keys(\Drupal::moduleHandler()->getModuleList()))) {
	return drush_set_error(dt('Module @module_name not found.', ['@module_name' => $module_name]));
  }
  // Confirm re-registering.
  if (drush_confirm(dt("Are you sure you want to re-register migrate configs?"))) {
	drush_print("Removing.....");
	if (_my_module_remove_configs($module_name)) {
  		drush_print("Removed successfully");
	}
	else {
  		drush_print("There was a problem removing old configs");
	}

	if (_my_module_install_config($module_name)) {
  		drush_print("Configuration was successfully reset.");
	}
	else {
  		drush_print("There was a problem resetting configuration for " . $module_name);
	}

	return TRUE;
  }

}

/**
 * Helper module to remove configs for a given module.
 * @param  string $module_name module name.
 * @return bool            	TRUE if the files exist.
 */
function _my_module_remove_configs($module_name) {
  // Get the path for the module we want to remove configs for.
  $module_path = DRUPAL_ROOT. base_path() . drupal_get_path('module', $module_name) . '/config/install';

  // Get all of the migration config names.
  $configs = array();
  $files = file_scan_directory($module_path, '/migrate_plus/');

  if ($files) {
	foreach ($files as $file) {
  		$configs[] = $file->name;
	}

	// Delete each config using configFactory.
	foreach ($configs as $config_name) {
  		drush_print("..." . $config_name);
  		\Drupal::configFactory()->getEditable($config_name)->delete();
	}

	return TRUE;
  }

  return FALSE;
}

/**
 * Helper module to install configs for a given module.
 * @param  string $module_name module name.
 * @return bool            	TRUE if no error from installDefaultConfig.
 */
function _my_module_install_config($module_name) {
  // Initiate config installer for given module.
  \Drupal::service('config.installer')->installDefaultConfig('module', $module_name);

  return TRUE;
}

Now to re-register migrations you can just run drush mrm my_module!

 

Additional Resources
How to Think About Drupal 8 | Blog Post
5 Minutes on Configuring Your Drupal Site | Video
How to Prepare Your Website for Drupal 8 | eBook

Thumbnail

Meet team member, Chris Runo

Chris brings four years of Drupal experience to his role as a Senior Drupal Developer at Mediacurrent. Throughout his web development career, Chris has gained a unique perspective on what...

Learn more about Chris >