This article is one of Metal Toad's Top 20 Drupal Tips. Enjoy!
October 11, 2017: Now updated for Drupal 8.4!
Drupal 8 provides a flexible, plugin-based architecture for migrating data into a site. In Part 2 of this series, we explored how to migrate users from a Drupal 7 site. We will now expand on this by migrating Taxonomy vocabularies and terms from a Drupal 7 site into Drupal 8.
This article continues our work from Part 2. The code examples pick up where that post left off. If you are trying this code out yourself, it is recommended to start building your custom migration module according to the examples in that post.
We'll start by writing a new migration definition, then write a source plugin to match. This will expand upon the "Migrate Custom" module we created in Part 2.
The migration definition:
Create a file named modules/migrate_custom/config/install/migrate_plus.migration.custom_taxonomy_vocabulary.yml
with the following contents:
id: custom_taxonomy_vocabulary label: Drupal 7 taxonomy vocabularies migration_group: custom dependencies: enforced: module: - migrate_custom source: plugin: custom_taxonomy_vocabulary process: vid: - plugin: machine_name source: machine_name - plugin: dedupe_entity entity_type: taxonomy_vocabulary field: vid length: 32 label: name name: name description: description hierarchy: hierarchy module: module weight: weight destination: plugin: entity:taxonomy_vocabulary
Here we have examples of a few plugins not seen in the previous post:
machine_name
converts the string into a valid machine name.dedupe_entity
prevents machine name conflicts, which would cause imported data to overwrite existing data. For example, a machine name "foo" would be renamed to "foo_2" if name "foo" already existed.The source plugin
To define the source of our vocabulary data, we create a new file modules/migrate_custom/src/Plugin/migrate/source/Vocabulary.php
with the following contents:
<?php /** * @file * Contains \Drupal\migrate_custom\Plugin\migrate\source\Vocabulary. */ namespace Drupal\migrate_custom\Plugin\migrate\source; use Drupal\migrate\Row; use Drupal\migrate\Plugin\migrate\source\SqlBase; /** * Drupal 7 vocabularies source from database. * * @MigrateSource( * id = "custom_taxonomy_vocabulary", * source_provider = "taxonomy" * ) */ class Vocabulary extends SqlBase { /** * {@inheritdoc} */ public function query() { $query = $this->select('taxonomy_vocabulary', 'v') ->fields('v', array( 'vid', 'name', 'description', 'hierarchy', 'module', 'weight', 'machine_name' )); return $query; } /** * {@inheritdoc} */ public function fields() { return array( 'vid' => $this->t('The vocabulary ID.'), 'name' => $this->t('The name of the vocabulary.'), 'description' => $this->t('The description of the vocabulary.'), 'help' => $this->t('Help text to display for the vocabulary.'), 'relations' => $this->t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'), 'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'), 'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'), 'parents' => $this->t("The Drupal term IDs of the term's parents."), 'node_types' => $this->t('The names of the node types the vocabulary may be used with.'), ); } /** * {@inheritdoc} */ public function getIds() { $ids['vid']['type'] = 'integer'; return $ids; } }
Note: this file was adapted from the Drupal 6 version in Drupal 8 core. For the original file, see core/modules/migrate_drupal/src/Plugin/migrate/source/d6/Vocabulary.php
The structure of this file is similar to the User source plugin in the previous article. However, because all the data we need is stored in the `taxonomy_vocabulary` table in the source database, we do not need to define the prepareRow()
method.
We can use a second migration definition to migrate our taxonomy terms. Create the following file:
modules/migrate_custom/config/install/migrate_plus.migration.custom_taxonomy_term.yml
id: custom_taxonomy_term label: Drupal 7 taxonomy terms migration_group: custom dependencies: enforced: module: - migrate_custom source: plugin: custom_taxonomy_term process: tid: tid vid: plugin: migration_lookup migration: custom_taxonomy_vocabulary source: vid name: name description: description weight: weight parent: - plugin: skip_on_empty source: parent - plugin: migration_lookup migration: custom_taxonomy_term changed: timestamp destination: plugin: entity:taxonomy_term migration_dependencies: required: - custom_taxonomy_vocabulary
In this migration, we make use of the migration
process plugin for two of our properties, the vocabulary ID and the parent term ID. This preserves these references in case the referenced entity's ID or machine name changed during the import.
Some machine names and/or IDs will likely change when running your import. This is to be expected, especially because Drupal 8 stores taxonomy vocabularies in the 'config' table, where they are accessed by their machine names instead of by the numeric IDs used in Drupal 7. Fortunately for us, the Migrate module records a map of the old and new IDs in the database. We can then use the migration
source plugin to easily look up the old ID or machine name.
The source plugin
To define the source of our term data, we create a new file modules/migrate_custom/src/Plugin/migrate/source/Term.php
with the following contents:
<?php /** * @file * Contains \Drupal\migrate_custom\Plugin\migrate\source\Term. */ namespace Drupal\migrate_custom\Plugin\migrate\source; use Drupal\migrate\Row; use Drupal\migrate\Plugin\migrate\source\SqlBase; /** * Drupal 7 taxonomy terms source from database. * * @todo Support term_relation, term_synonym table if possible. * * @MigrateSource( * id = "custom_taxonomy_term", * source_provider = "taxonomy" * ) */ class Term extends SqlBase { /** * {@inheritdoc} */ public function query() { $query = $this->select('taxonomy_term_data', 'td') ->fields('td', array('tid', 'vid', 'name', 'description', 'weight', 'format')) ->distinct(); return $query; } /** * {@inheritdoc} */ public function fields() { return array( 'tid' => $this->t('The term ID.'), 'vid' => $this->t('Existing term VID'), 'name' => $this->t('The name of the term.'), 'description' => $this->t('The term description.'), 'weight' => $this->t('Weight'), 'parent' => $this->t("The Drupal term IDs of the term's parents."), ); } /** * {@inheritdoc} */ public function prepareRow(Row $row) { // Find parents for this row. $parents = $this->select('taxonomy_term_hierarchy', 'th') ->fields('th', array('parent', 'tid')) ->condition('tid', $row->getSourceProperty('tid')) ->execute() ->fetchCol(); $row->setSourceProperty('parent', $parents); return parent::prepareRow($row); } /** * {@inheritdoc} */ public function getIds() { $ids['tid']['type'] = 'integer'; return $ids; } }
Remember that migration definitions are configuration entities. To reload the configuration, we need to uninstall and reinstall our module. Here's a handy Drush command to do this:
drush pm-uninstall migrate_custom -y && drush en migrate_custom
As we did with our user migration, we now run the migration using Drush.
drush migrate-import custom_taxonomy_vocabulary drush migrate-import custom_taxonomy_term
Next post: Migrating Nodes from Drupal 7.