Skip to main content

Adding Extra Source Properties in Drupal Migrations

Sometimes it is necessary to add extra information in our entities to migrate the content to Drupal 8.

Let’s say that I want to migrate the articles content type from Drupal 7 to Drupal 8 and as part of that I want to migrate the tags with my articles.

There are two options:

  • Create a new migration for the taxonomy terms and later link them using the migration_lookup plugin.
  • Make the article migration check to see if the terms exist already and if not then create them.

I will go with option two in this example. To do that I’m going to use the Migrate Plus module, which provides some handy plugins such as entity_generate.

The entity_generate process plugin receives a value and checks if there is an entity with that name. If the term exists then it uses it and if it does not then creates it (which is precisely what I need).

So, here is a snippet of the article migration YAML file using the entity_generate plugin:

id: blog
migration_group: Drupal
label: Blog
source:
  plugin: d7_node
  node_type: blog
destination:
  plugin: entity:node
process:
  status: status
  created: created
  field_tags:
    plugin: sub_process
    source: field_tags
    process:
      target_id:
      - plugin: entity_generate
        source: name
        value_key: name
        bundle_key: vid
        bundle: tags
        entity_type: taxonomy_term
        ignore_case: true
        …

In our field_tags field we are using the Drupal 7  field_tags Values We are going to read the entities and pass that value into the entity_generate plugin to create the entities. In this example, there is a problem,  the d7_node migrate plugin (included in the migrate module) provides the taxonomy terms IDs and this will make the entity_generate plugin to create some taxonomy terms using the IDs as the term names, and this is not what we want.

So what I need to do is to get from somewhere the terms names, not their ids, to do that I need to add an extra source property.

First, we need to create a new custom module and in there create a source plugin which extends the Node process plugin, something like this (let's say that our custom module’s name is my_migration):

Create the file:

my_migration/src/Plugin/migrate/source/MyNode.php

And the file should contain this code:

namespace Drupal\my_migration\Plugin\migrate\source;

use Drupal\migrate\Row;
use Drupal\node\Plugin\migrate\source\d7\Node;

/**
 * Adds a source property with the taxonomy term names.
 *
 * @MigrateSource(
 *   id = “my_node",
 *   source_module = "node"
 * )
 */
class MyNode extends Node {

  public function prepareRow(Row $row) {
    $nid = $row->getSourceProperty('nid');
    // Get the taxonomy tags names.
    $tags = $this->getFieldValues('node', 'field_tags', $nid);
    $names = [];
    foreach ($tags as $tag) {
      $tids[] = $tag['tid'];
    }
    if (!$tids) {
      $names = [];
    }
    else {
      $query = $this->select('taxonomy_term_data', 't');
      $query->condition('tid', $tids, 'IN');
      $query->addField('t', 'name');
      $result = $query->execute()->fetchCol();
      $names[] = ['name' => $result['name']];
      foreach ($result as $term_name) {
        $names[] = ['name' => $term_name];
      }
    }
    $row->setSourceProperty('field_tags_names', $names);
    return parent::prepareRow($row);
  }
}

It does the following things:

  • It reads the nid of the article
  • Gets all the terms IDs of the field_tags field
  • For each ID, it gets the name of the term and put it in the `names` array
  • Finally, it sets this value inside the $row using the `setsourceProperty` method.

Now our rows will have a property called fields_tags_names with the terms names, and we can pass this data to the entity_generate plugin.

We need to make a few adjustments in our initial migration file,  first and most important update the  source plugin to use our new source plugin:

source:
  plugin: my_node
  …

And update the source in the field_tags field to use the new  field_tags_names source property.

  …
  field_tags:
    plugin: sub_process
    source: field_tags_names
  ….

The final migration file looks like this:

id: blog
migration_group: Drupal
label: Blog
source:
  plugin: my_node
  node_type: blog
destination:
  plugin: entity:node
process:
  status: status
  created: created
  field_tags:
    plugin: sub_process
    source: field_tags_names
    process:
      target_id:
      - plugin: entity_generate
        source: name
        value_key: name
        bundle_key: vid
        bundle: tags
        entity_type: taxonomy_term
        ignore_case: true
        …

And that’s it; if we run the migration, it will create on the fly the terms if they do not exist and use them if they do exist.

Comments

2019 November 16
Sia

Permalink

Thank you very much for this…

Thank you very much for this! Great walkthrough.

I had to remove

$names[] = ['name' => $result['name']];

above the foreach, it was adding an empty first value to the names array causing failure due to an empty name.

Add new comment

The content of this field is kept private and will not be shown publicly.

Markdown

The comment language code.
CAPTCHA Please help us focus on people and not spambots by answering this question.