Suppose you are migrating articles from an external site into Drupal article nodes. These articles may reference each other, so in Drupal you have created a node reference field on articles, field_article_ref. The references may be circular - article A may reference article B, and B in return references A. Now, the natural way to set this up in your Migration class is:

class ArticleMigration extends Migration {
  public function __construct() {
...
    $this->addFieldMapping('field_article_ref', 'ref_id')
         ->sourceMigration(array('Article'));
...

sourceMigration() takes an array of migration machine names as an argument.

This tells the Migrate module to use ref_id (the ID of the referenced article in the source system) to lookup the Drupal node that was migrated from the referenced article, and to use that node's ID to populate field_article_ref. But, when you're importing article A, article B hasn't been imported yet, so there is no Drupal node to use to populate field_article_ref. By default, field_article_ref will be left empty.

The Migrate module provides a solution to this chicken-and-egg problem: stub nodes. If the migration referenced in sourceMigration() implements a createStub() method, which creates a dummy node and returns its ID, then if the lookup of ref_id fails createStub will be called and the resulting node used to fill in field_article_ref. Then, when article B finally runs through the migration, the actual content of article B will replace the dummy data and everything will be hunky-dory.

Your createStub method should look something like:

  protected function createStub($migration, array $source_id) {
    $node = new stdClass();
    $node->title = t('Stub for @id', array('@id' => $source_id[0]));
    $node->body[LANGUAGE_NONE][0]['value'] = t('Stub body');
    $node->type = $this->destination->getBundle();
    $node->uid = 1;
    $node->status = 0;
    node_save($node);
    if (isset($node->nid)) {
      return array($node->nid);
    }
    else {
      return FALSE;
    }
  }

Pretty simple - create a node object, fill in the essential data, and save it.

If you need to handle things more dynamically -- for example, if you're pulling the source ID out of other fields -- you can call the method behind ->sourceMigration() directly:

  public function prepareRow($row) {
    ...
    $source_id = $this->findReferencedNid($row->body);
    $row->ref_id = $this->handleSourceMigration('SourceMigration', $source_id);
    ...
  }

Note that if you're doing it yourself, you should NOT append ->sourceMigration to the field mapping.

Using other destinations

The stub solution will also work for non-node migrations. Any migration class can use stubs. (Files, however, may not work as expected.)

For example, if you were to pull in Menu Links for a migration, you may find that some parent links don't exist. In that case, you can use stubs to create the parent item.

  function createStub($migration, array $source_id) {
    $link = array(
      'menu_name' => 'mymenu',
      'link_path' => 'user', // must be a valid path
      'link_title' => 'Stub',
    );
    if ($mlid = menu_link_save($link)) {
      return array($mlid);
    }
    else {
      return FALSE;
    }
  }

In this case, the $source_id would be the parent id of the menu link being saved by the current migration row.

Comments

mnlund’s picture

Wow, this was really nifty. Imagine someone has solved the chicken-egg-problem. Lovely.

donquixote’s picture

It should be mentioned, this currently does not work for files.
See #1314748-6: Files need to be resaved on update

The migration will create new files, instead of updating existing ones.

nlisgo’s picture

I have an instance where a node can reference itself. So creating a stub node will not work without some clean up afterwards. How might this be achieved?

Cameron Tod’s picture

I had a similar issue migrating book nodes. The short answer is to use parent::handleSourceMigration('MigrationName', $source_nid); in a complete() function to lookup the migrated nid only when required.

Have a look at my post about migrating book nodes for more detail: http://drupal.org/node/1513766

fonant’s picture

If the reference is to the node itself, the handleSourceMigration() method returns an empty array, and no stub is created. In a complete() method you can test for no returned NID and substitute the $node's nid if needed.

public function complete($node, stdClass $row) {
  ...
  $new_nid = $this->handleSourceMigration('mySourceMigration', $old_nid);
  if (!$new_nid) {
    echo "WARNING: Linking to myself\n";
    $new_nid = $node->nid;
  }
  ...
}

http://www.fonant.com - Fonant Ltd - Quality websites

danylevskyi’s picture

Thanks! Very helpful information.

darrylri’s picture

Note that the body field needs to have a language and delta. For example:

 $node->body[LANGUAGE_UNDEFINED][0] = t('Stub body');

Media module was very unhappy about the code in the original article.

rvarkonyi’s picture

In my case I had to make sure the stub is published, otherwise entityreference's validateReferencableEntities() method complained and the referencing entity wouldn't get created because of field validation:


  $node->status = 1;

Hope it helps someone.

kurtfoster’s picture

I needed to import multiple fields to a node reference field that also had the issue of multiple source rows, as in Multiple source data rows. It ended up being really easy, I just combined the separator and the sourceMigration and it worked perfectly.

$this->addFieldMapping('field_stuff', 'stuff_id')->separator(',')->sourceMigration(array('MigrateStuff'));

MacSim’s picture

Hello !

When my migration creates stubs, my nodes get an auto-path generated from the temporary title that has been set in the code. But when the stub is updated by the real node values, there's no update of the path even if I can see that pathauto is still set to TRUE (well 1) and path set to empty (ie. '').

I've tried something like the following but it's not working :s

  public function complete($entity, $row) {
    pathauto_node_update_alias($entity, 'update');
  }

Does someone know how I can achieve an update of the path during a migration update using pathauto ?

EDIT : I am such an idiot sometimes. There was no update of the path because pathauto_update_action was set to 0... It works fine now !

elephant.jim’s picture

Unfortunately, PHP doesn't consider NULL as matching the Migration class in the createStub() signature, and in many instances that's the value that's passed. protected function createStub(Migration $migration, array $source_id) should be protected function createStub($migration, array $source_id)

jorge.alves’s picture

Hi,
If you want to keep the same source id:
$this->addFieldMapping('is_new', NULL, FALSE)->defaultValue(TRUE);
$this->addFieldMapping('nid', 'nid');
This seems not to work:
"Incoming nid xx and map destination nid yy don't match"
Anyone having the same issue?

migueldiasbrito’s picture

Hi,
I had the same issue while trying to keep the source id. I overrode the create_stub function in my Migrate class:

protected function createStub($migration, $source_key) {
    migrate_instrument_start('DrupalNodeMigration::createStub');
    $node = new stdClass;
    
    //Add original nid
    $node->nid = $source_key[0];
    $node->is_new = TRUE;
    
    $node->title = t('Stub');
    $node->body = array(LANGUAGE_NONE => array(array("value" => t('Stub body'))));
    $node->type = $this->destination->getBundle();
    $node->uid = 1;
    node_save($node);
    watchdog('CustomNodeMigration', json_encode((array) $node));
    migrate_instrument_stop('DrupalNodeMigration::createStub');
    if (isset($node->nid)) {
      return array($node->nid);
    }
    else {
      return FALSE;
  }
}

Then we have to prevent duplicate entries in the database, which I did by overriding the function prepare:

public function prepare(stdClass $entity, stdClass $row) {

    $values = db_select('node', 'n')
      ->fields('n', array('vid', 'tnid'))
      ->condition('nid', $entity->nid)
      ->execute()
      ->fetchAssoc();

    if(!empty($values))
    {
      $entity->is_new = FALSE;
    }

  }

Hope it helps someone stumbling in the same problem.

racheljohn123’s picture

looking for this for a long time this helps
Thanks

asauterChicago’s picture

None of the examples or documentation for the D8 Migrate module show how to make stubs. They talk about it, but it's extremely difficult to find any info on how to create stubs with D8. Seems like a pretty important missing piece.

Gold’s picture

Damnit... I read all the way through the comments above before hitting this one which appears to be the only one relevant to my situation.

--
Regards
Gold
Drupal Code Monkey

alison’s picture

(in case someone else lands here!)

So, it's not official Drupal documentation, and I agree we should have some documentation on d.o about D8 stubbing! But--

https://www.phase2technology.com/blog/drupal-8-migrations
^^ This tutorial explains how to do it in the context of a hierarchical taxonomy, where the parent term ID might not have been migrated in yet -- "ctrl + F" the page at that link for "hierarchical," and you should find yourself in a paragraph (right *below* a code snippet with an example of what it's talking about) explaining the parent parameter stuff.

mikelutz’s picture

It's different in D8. In d8, the migration_lookup plugin handles stubbing automatically unless you specifically tell it not to create stubs. I agree the documentation needs to reflect this better.