While writing a field collections migration, I got tired of digging through my source database looking for where various bits of needed data were stored (across a bunch of tables.) I thought it would be much easier if I could use an entity field query instead of an SQL query as a source. I've added a MigrateSourceEntity plugin to the D7 folder.

This is based on https://www.drupal.org/node/1883112. I opted to add it to migrate_d2d since it really is only applicable for a Drupal(7)-to-Drupal migration.

Here's an example of how I used it for the field collection migration. It's worth noting that this example is overly complicated by some field_collections-specific gotchas; however, the essence of the MigrateSourceEntity's usage is captured in the $this->source() assignment in __construct() and the query() function.

class MyExhibitorRepFieldCollection extends DrupalMigration {

  public function __construct(array $arguments) {
    parent::__construct($arguments);

    $this->source = new MigrateSourceEntity(
      $this->sourceConnection,
      $this->query(),
      $this->sourceFields,
      null,
      [
        'wrap_entity'=> true,
        'source_fields' => [
          'field_rep_email',
          'field_rep_name',
          'field_rep_phone',
        ],
      ]
    );

    $this->destination = new MyMigrateDestinationFieldCollection(
      'field_exhibitor_reps',
      array('host_entity_type' => 'commerce_customer_profile')
    );

    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
      ),
      MyMigrateDestinationFieldCollection::getKeySchema()
    );

    $this->addFieldMapping('host_entity_id', 'host_entity')
      ->sourceMigration('ExhibitorProfiles');

    $this->addSimpleMappings([
      'field_rep_name',
      'field_rep_phone',
      'field_rep_email',
    ]);

    $this->addUnmigratedDestinations(['path']);

    $this->addUnmigratedSources([
      'item_id',
      'revision_id',
      'field_name',
      'archived',
      'url',
    ]);

  }

  /**
   * The base source query for this migration.
   *
   * @return \EntityFieldQuery
   */
  protected function query() {

    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'field_collection_item');
    $query->entityCondition('bundle', 'field_exhibitor_reps');

    return $query;
  }

  public function prepareRow($row) {
    if (parent::prepareRow($row) == false) {
      return false;
    }

    // This is a bit of a hack, but for some reason the field collection item's
    // hostEntityId() function only returns NULL. Get the host id directly from
    // the source database. The field mapping's sourceMigration property will
    // automatically cross reference this is with the new id from the
    // ExhibitorProfiles migration.
    $result = Database::getConnection('default', $this->sourceConnection)
      ->select('field_data_field_exhibitor_reps', 'f')
      ->fields('f', ['entity_id'])
      ->condition('f.field_exhibitor_reps_value', $row->source->item_id)
      ->condition('f.field_exhibitor_reps_revision_id', $row->source->revision_id)
      ->execute();

    $row->host_entity = $result->fetchField();

    $row->field_rep_email = $row->field_rep_email[LANGUAGE_NONE][0]['email'];
    $row->field_rep_name = $row->field_rep_name[LANGUAGE_NONE][0]['value'];
    $row->field_rep_phone = $row->field_rep_phone[LANGUAGE_NONE][0]['value'];

  }
}

class MyMigrateDestinationFieldCollection extends MigrateDestinationFieldCollection {
  public function import(stdClass $collection, stdClass $row) {
    // MigrateDestinationFieldCollection assumes host_entity_id is a single key
    // Since this customer profiles use compound keys (entity_id & revision_id) use
    // only the first key in the array.
    $collection->host_entity_id = $collection->host_entity_id['destid1'];
    return parent::import($collection, $row);
  }
}
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jayusa123 created an issue. See original summary.

pvdjay’s picture

Issue summary: View changes
pvdjay’s picture

So after playing around a bit, I've discovered that using db_set_active() can be pretty dicey when it comes to caching. The issue is that field definitions that are read from one database are cached and subsequent database calls may fail when those tables are not found in the other database. All I could think of to resolve the issue was to clear the field cache after each database switch. Unfortunately, that results in a big performance hit; my testing shows a slow down of an order of magnitude or greater.

I also added invocations of destination field handlers to get subfields for any applicable fields; however, this is a work in progress so far.