Name currently has a migrate handler included with it (NameMigrateFieldHandler) that was added as part of #1734922: Name Field Migrate support with import example (single full name text field separated and split into the individual components).

However, before that issue was opened, Migrate Extras already included a name handler (MigrateNameHandler), added as part of #1468010: Name field support.

The handlers definitely should not exist in both places. Migrate specifically states that handlers should live with the fields themselves.

The handler included with Migrate Extras is better documented, and seems to work better, so I'd recommend using it to replace the current handler provided by Name.

After this is committed, we should open a corresponding issue with Migrate Extra to delete the name handler there, so that it doesn't exist in two places (possibly causing errors if people have both modules enabled).

Patch to follow.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dane Powell’s picture

Status: Active » Needs review
FileSize
3.18 KB
Alan D.’s picture

+    return isset($return) ? $return : NULL;

Should be something like

+    return isset($return[$language]) ? $return : NULL;

If you want to insert some Name specific logic, (personally not fussed about this), these can be used.

$components = array_filter($field_info['settings']['components']);
$minimum_components = array_filter($field_info['settings']['minimum_components']);

I would not enforce $minimum_components, possibly unknown / non-existent in the data source. The data can be fixed when the entity is edited.

Filtering by allowed $components could prevent inserting data into the DB that is never accessible.

    // Setup the standard Field API array for saving.
    $components = array_filter($field_info['settings']['components']);
    foreach ($values as $value) {
      $item = array('given' => $value) + array_intersect_key($arguments, $field_info['columns']);
      $item = array_intersect_key($item, $components);
      if (array_filter($item)) {
        $return[$language][] = $item;
      }
    }

Finally, the arguments here only appear to support a singular name field item. What happens with multiple value fields?

Dane Powell’s picture

FileSize
3.83 KB

Alan, thanks for the great feedback. I changed the return syntax and added support for components. I also added support for multi-value fields.

I actually haven't had a chance to test this yet, since I'm away from my development machine, but will do so in a few hours.

Dane Powell’s picture

FileSize
4.75 KB

Okay, here's a final cut of this. It's passed code review, and works for me on a few test cases.

This uses the 'given' name as the primary column, and adds the fields() function to support this - I don't think the original version included with migrate_extras quite worked, because it didn't implement fields(). The documentation is also improved to clarify this.

Alan D.’s picture

Looking better :)

1) Tidied up the doc comment a bit.

/**
 * Allows using Name fields as Migrate destinations.
 *
 * The primary value passed to this field handler must be the 'given' component
 * and arguments are used to pass additional name components.
 *
 * Add the source field mappings to the argument array then add null mappings
 * to avoid having fields flagged as as unmapped.
 *
 * Example usage:
 * @code
 *   $arguments = array(
 *    'title' => array('source_field' => 'profile_title'),
 *    'middle' => array('source_field' => 'profile_middle_name'),
 *    'family' => array('source_field' => 'profile_last_name'),
 *   );
 *   // The given component should be passed in as the primary value.
 *   $this->addFieldMapping('field_name', 'profile_first_name')
 *        ->arguments($arguments);
 *   // Since the excerpt is mapped via an argument, add a null mapping so it's
 *   // not flagged as unmapped.
 *   $this->addFieldMapping(NULL, 'profile_title');
 *   $this->addFieldMapping(NULL, 'profile_middle_name');
 *   $this->addFieldMapping(NULL, 'profile_last_name');
 * @endcode
 *
 * @see MigrateDefaultFieldHandler
 */
class MigrateNameHandler extends MigrateFieldHandler {

2) I am not sure on functionality of the method, but this should be the same.

  public function fields($field_type, $field_instance) {
    $fields = _name_translations();
    // The given component is mapped directly to the field name,
    // so don't include it here among the sub-fields.
    unset($fields[$this->primaryColumn]);
    return $fields;
}

3) Since the code could potentially skip an item, this ensures that the returned delta values are consistent (although I think that cores field system does an array_values() here too)

- $return[$language][$delta] = $item;
+ $return[$language][] = $item;

4) I think that the delta variable can be moved into the for loop, as the delta value of the incoming values. 2 less loc

-    $delta = 0;
-    foreach ($values as $value) {
+    foreach ($values as $delta => $value) {
...
-      $delta++;

5) Without have a test environment, are the arguments in this structure? All I can say is "Ycks", but no comment on Migrate itself

// single
$arguments = array('given' => 'abc', ...)
// multiple
$arguments = array(array('given' => 'abc', ...), array('given' => 'dfg', ...));

I have one local migrate environment, but I can not test this as it is a XML feed that uses a JSON encoded request to get a node from the source and the MyClassXMLMigration::prepare($entity, $source_row) simply does this (without all of the special business logic and image / file field handing):

function MyClass extends XMLMigration {
  public function prepareRow($row) {
    $row->source_entity = MyClass::retriveNodeEntityRemoteServer($row->xml->nid);
  }

  public function prepare($entity, $source_row) {
    $entity->is_migrate_update = 1;
    if (isset($source_row->source_entity)) {
      foreach (field_info_instances($this->source_entity_type, $this->source_bundle) as $field_name => $instance) {
        if ($items = field_get_items($this->source_entity_type, $source_row->source_entity, $field_name)) {
          $field = field_info_field($field_name);
          $langcode = field_language($this->source_entity_type, $source_row->source_entity, $field_name);
          $entity->$field_name = array($langcode => $items);
        }
      }
    }
  }
}

The node entity has about 50 fields, 10 or so with no migrate support, but this just works and handling the business logic required where you have $source _entity and $destination_entity nodes is trivial ;)

Alan D.’s picture

I guess that you have come from using MigrateExtras, to just this?

So I wonder what will happen for users using just Name migrate and the Migrate class registry?

Maybe an empty update function that should (hopefully) force any caches to be flushed

/**
 * An empty update function to ensure that any Migrate
 * class caches are flushed.
 */
function name_update_7003() {}

[edit]
Actually looking at migrate_get_module_apis() [migrate 7.x-2.6-rc1], this is not needed :)

Dane Powell’s picture

Thanks, I'll take a look at this tomorrow. I'm basing this all on existing Migrate code, so if there are problems, they are Migrate's fault, not mine :) Seriously though, I didn't want to change the code too much, so that we can stay "on the reservation" as much as possible. If there are problems with the code potentially skipping items, or a totally fugly arguments structure, we should get that fixed upstream. For now, I'd prefer to get this at least committed, and make improvements later.

Alan D.’s picture

"totally fugly arguments structure" is by design from memory, I think I have seen these before :/

Happy to commit with #1 - #3

Dane Powell’s picture

Alan you seem to be pretty knowledgeable about Migrate- do you know why the arguments-based approach was chosen, instead of being able to simply map directly to field parts (i.e. $this->addFieldMapping('field_name:given', 'profile_title');)?

Alan D.’s picture

Nope, not sure. First time I saw this I thought ffs, not only do you need to know the field schema structure you need to know the wield mapping rules... maybe a work around for the migrate internals?

Dane Powell’s picture

FileSize
4.65 KB

Okay, here's a re-roll of #4 with the improved documentation and $delta fix. Thanks!

Dane Powell’s picture

I opened an issue with Migrate to try to get this question answered: #2223095: Clarify docs around mapping of field parts

Dane Powell’s picture

Okay, now I feel like a bit of an idiot. Just for fun, I tried actually mapping the fields directly (instead of using arguments), and it worked just fine. This seems like a much saner approach- I don't know why I assumed that using arguments was the the only solution. I think it would make sense to rewrite the documentation on the module to use this approach rather than arguments. Do you agree?

Dane Powell’s picture

FileSize
4.17 KB

Alright, here's a patch with updated documentation based on #2223095: Clarify docs around mapping of field parts. I think this is good to commit.

Dane Powell’s picture

Dane Powell’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
4.56 KB

This patch incorporates the fix from #2155749: Migrate API handler change.

We've been using this quite successfully internally, think you can go ahead and commit it?

Alan D.’s picture

Status: Reviewed & tested by the community » Needs review

Can someone actively using a name migration actual confirm that this will not cause a fatal error due to a potential stale class registry please? I have no active migration environments to test atm :)

@Dane Powell
Does this require a change in anyones migration scripts? If so, a quick short summary would be good to go into the release page when the next one is rolled out would be fantastic!

Dane Powell’s picture

I set up a development site with the stock version of Name, ran an import using a name field, applied the patch in #16, and re-ran the import. It worked fine- no errors. It would be nice if someone else could test, but I don't know how long it'll be before that someone comes along :)

No user intervention should be required. Migrate dynamically finds appropriate handlers for a given field type, so it should automatically start using this one.

However, users of Migrate Extras should disable that module or disable its name handler in the Migrate config UI. Once we commit this patch, I'll submit another to Migrate Extras to remove the duplicate handler.

valthebald’s picture

Status: Needs review » Reviewed & tested by the community

@Alan D: I confirm that the patch from #16 does not cause any death screens. We actively use migrations with name fields, and they work only with this patch

jiv_e’s picture

I have used this patch for a simple use case and it works fine. I have a D7->D7 migration with 'given' and 'family' components.

So the source is a name field on Drupal. I first thought it would work like this for the 'given' component.

        // The given component should be mapped as the primary value.
        $this->addFieldMapping($field, $field);

...but had to do it like this.

        // The given component should be mapped as the primary value.
        $this->addFieldMapping($field, $field.':given');

Here's the whole field mapping section:

        // The given component should be mapped as the primary value.
        $this->addFieldMapping($field, $field.':given');
        // Now map other name components.
        $this->addFieldMapping($field.':family', $field.':family');
edvanleeuwen’s picture

I am interested in this as well. How can I help testing/reviewing? Is there a migration example I could change according to my situation?

edvanleeuwen’s picture

Could anyone explain why the 'given' part has been chosen as primary and not 'family'?

(Background: I noticed when migrating organizations in CRM Core that the family name is the one displayed. So at first sight, it looks like these contact types have been imported with an empty name. The workaround is to edit the name field and set family name hidden, given name visible).

rmcom’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Could this patch be included into the next version? Thank you!

I am working with CRM Core (specifically data imports via the Migration module).

rmcom’s picture

Status: Patch (to be ported) » Reviewed & tested by the community

This patch is necessary for CRM Core imports to run.

Importing data into CRM Core

rmcom’s picture

Status: Reviewed & tested by the community » Patch (to be ported)
joelpittet’s picture

Status: Patch (to be ported) » Reviewed & tested by the community

leave the status, patch to be ported is for backporting committed patches to older versions

  • Alan D. committed 2b3d00d on 7.x-1.x authored by Dane Powell
    Issue #2221717 by Dane Powell, Alan D., jiv_e, valthebald, joelpittet:...
Alan D.’s picture

Status: Reviewed & tested by the community » Fixed

Such an old stale issue, but let me know if I need to add a change record or a simple note to the release page itself

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.