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
Wow, this was really nifty.
Wow, this was really nifty. Imagine someone has solved the chicken-egg-problem. Lovely.
It should be mentioned, this
It should be mentioned, this currently does not work for files.
See #1314748-6: Files need to be resaved on update
Reference itself
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?
You can use parent::handleSourceMigration()
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
$this->handleSourceMigration() does nothing for self-mapping
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.
http://www.fonant.com - Fonant Ltd - Quality websites
Very helpful
Thanks! Very helpful information.
D7 body field
Note that the body field needs to have a language and delta. For example:
Media module was very unhappy about the code in the original article.
In my case I had to make sure
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:Hope it helps someone.
Multiple source data rows and Stubs
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'));
update, stubs and pathauto
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
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 !
createStub() signature shouldn't have the "Migration" class
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 beprotected function createStub($migration, array $source_id)
Incoming nid xx and map destination nid yy don't match
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?
Hi,
Hi,
I had the same issue while trying to keep the source id. I overrode the create_stub function in my Migrate class:
Then we have to prevent duplicate entries in the database, which I did by overriding the function prepare:
Hope it helps someone stumbling in the same problem.
looking for this for a long
looking for this for a long time this helps
Thanks
We need Drupal 8 examples.
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.
+1
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
drupal 8
(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.
It's different in D8. In d8,
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.