Hi,

I'm setting up an import where the bundle type is determined based on information in the incoming data and several extra fields are added for one of the content types (basically the incoming HTML code for the page is parsed using QueryPath and mapped to different fields, depending on the node type).

However, the field handlers for the extra fields are not being called for fields which are not a part of the default type (the one specified in the call to MigrateDestinationNode).

After debugging the code, I believe I found where the error occurs:

  // from plugins/destinations/fields.inc
  public function prepare($entity, stdClass $row) {
    migrate_instrument_start('MigrateDestinationEntity->prepareFields');
    // Look for Field API fields attached to this destination and handle appropriately
    $migration = Migration::currentMigration();
    $destination = $migration->getDestination();
    $entity_type = $destination->getEntityType();
    $bundle = $destination->getBundle(); // THIS ONLY GETS THE FIELDS FOR THE DEFAULT BUNDLE!!!
    $instances = field_info_instances($entity_type, $bundle);
    ...
  }

By changing $destination->getBundle(); to $entity->type, I was able to successfully execute the handlers. I'm not really familiar with how to make patches here yet, nor do I know if this is the best solution, but I thought it should be addressed in case anyone else tries to do this.

Comments

mikeryan’s picture

Category: bug » feature

$entity->type would work for nodes, but not in the general case for entities supporting bundles (e.g., vocabularies).

What you're attempting is unsupported - the static nature of a destination's bundle is a fundamental assumption of the design (e.g., in the migration detail pages, displaying the fields for the bundle). Supporting dynamic bundles in the general case would be quite a bit of work.

mikeryan’s picture

Status: Active » Closed (won't fix)
claudiu.cristea’s picture

Title: Field handlers not executed when dynamically setting $node->type » Bundle should be extracted from entity (not from destination)
Version: 7.x-2.3 » 6.x-2.x-dev
Status: Closed (won't fix) » Active

I'm reopening this. I have the same scenario. I'm extending MigrateDestinationNode to allow migration to different kind of node types (bundles). Here's my destination:

class MigrateDestinationNodeAllTypes extends MigrateDestinationNode {
  public function __construct(array $options = array()) {
    parent::__construct(NULL, $options);
  }
  
  public function import(stdClass $node, stdClass $row) {
    $node->type = $row->type;
    return parent::import($node, $row);
  }
}

But this fails in plugins/destinations/fields.inc here:

class MigrateFieldsNodeHandler extends MigrateDestinationHandler {
...
  public function prepare($entity, stdClass $row) {
    migrate_instrument_start('MigrateDestinationEntity->prepareFields');
    // Look for Field API fields attached to this destination and handle appropriately
    $migration = Migration::currentMigration();
    $destination = $migration->getDestination();
    $bundle = $destination->getBundle();
    $info = content_types($bundle);

and also in ->complete() because $destination->getBundle(); returns NULL.

If we change $bundle = $destination->getBundle(); to:

EDIT: Corrected the code.

    list(,, $bundle_key) = entity_extract_ids($destination->getEntityType(), $entity);
    $bundle = $entity->$bundle_key;

and add a Drupal 6 backport function in includes/d7.inc:

function entity_extract_ids($entity_type, $entity) {
  if ($entity_type == 'node') {
    return array('nid', 'vid', 'type');
  }
  return FALSE;
}

This trick should allow us to handle multiple node types as a single destination. Note that this is nice handled in plugins/destinations/node.inc:


    else if (!isset($node->type)) {
      // Default the type to our designated destination bundle (by doing this
      // conditionally, we permit some flexibility in terms of implementing
      // migrations which can affect more than one type).
      $node->type = $this->bundle;
    }

I would provide a patch for this but I want to know maintainer point of view.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea

I'm taking this

claudiu.cristea’s picture

Status: Active » Needs review
StatusFileSize
new2.14 KB

Here's a 6.x-2.x patch.

mikeryan’s picture

The maintainer, I must admit, is unenthused. In the current design, the bundle is an integral part of the identity of the migration - for one thing, it determines what fields are available for mapping. Trying to use one class for different destination bundles still feels hacky for me - is there a reason you can't have multiple migrations (derived from a common class, differing only on the destination construction and any bundle-specific field mappings)?

claudiu.cristea’s picture

My case is not that important as the idea to keep MigrateDestinationNode as flexible as we can. In fact it was not my idea to extend MigrateDestinationNode in order to achieve a multi-node-type destination. Again, I'm posting a piece of code and the comment from plugins/destinations/node.inc (line 177) that talks itself that MigrateDestinationNode was designed to be extensible to a node-type agnostic class. Here it is:

    else if (!isset($node->type)) {
      // Default the type to our designated destination bundle (by doing this
      // conditionally, we permit some flexibility in terms of implementing
      // migrations which can affect more than one type).
      $node->type = $this->bundle;
    }

See the comment. It's the module author legacy that let us do that. In fact what I'm asking you is not to accept multi-node-type destination but only to get the the bundle value from entity instead of destination. What is wrong here? Why we wouldn't allow that?

Also, my patch was designed in a D7 style, using entity API to get the bundle. It's not hardcoded to nodes. The port to D7 it's straight -- I will provide a D7 patch too.

And, yes. My case is that I have a bunch of source objects stored in XML files (each record is a file). Destination are nodes having different types but same field instances. This is the client business model and it's senseless to argue here why the data structure has been designed in that way. Why should I write create migrations for each node-type if everything is the same except $node->type? It makes no sense.

frob’s picture

Version: 6.x-2.x-dev » 7.x-2.x-dev
Status: Needs review » Needs work

I would like this feature. I am migrating paragraphs and it makes sense to have the bundle not pre-selected. I am going to see if that can happen in the bundle destination class. If it can then this wouldn't need to be in the migration core classes.