Drupal Developer Tip: Multiple Entity References using Feeds Importer

Submitted by Brandon Cone on 05/29/2013 - 09:57:am

Feeds provides a powerful tool to facilitate data import into your site.  When combined with Feeds Tamper, there are few import problems which can't be solved to get content out of your old system and into your new Drupal system.  Multi-valued Entity Reference fields are one of those problems.

I ran into this problem when setting up and testing a product import for  a client.  They had data in csv files which contained all the product information as well as a list of related products.  Importing a single entity reference is stock feeds (or commerce feeds if importing product entities).  However, I couldn't find any solutions for importing multiple instances of an entity reference field so that I could, as part of the import, have products with 1 or more related products.

Here is the solution I came up with:

1.    Create a custom target callback for your field importer.  The targets array is keyed by the name of the target so it is necessary to differentiate your custom handler from the standard field handler for that entity reference field.

  1. /**
  2.  * Implements hook_feeds_processor_targets_alter().
  3.  */
  4. function my_module_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
  5.   foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
  6.     $info = field_info_field($name);
  7.  
  8.     if ($info['type'] == 'entityreference') {
  9.       $targets[$name .':custom_handler'] = array(
  10.         'name'        => check_plain($instance['label'] .t(' (Import multiple values into multi-instance field')),
  11.         'callback'    => 'my_module_set_target_entity_reference',
  12.         'description' => t(''),
  13.       );
  14.     }
  15.   }
  16. }

 

2.    Implement the callback function to populate the data into this multi-instance field.  It is important that when the data gets into your callback function that the values are in an array.  You can accomplish this by using Feeds Tamper to either combine multiple fields into a comma-separated list and then use the explode plugin to put the list into an array or, if they are all in one field, use the explode plugin to create an array based on the newline (%n) character.

  1. function cf_custom_set_target_entity_reference($source, $entity, $target, $value, $mapping) {
  2.   if (empty($value)) {
  3.     return;
  4.   }
  5.  
  6.   // Handle non-multiple valued fields.
  7.   if (!is_array($value)) {
  8.     $value = array($value);
  9.   }
  10.   $target = str_replace(':custom_handler', '', $target);
  11.   // Iterate over all values.
  12.   $info = field_info_field($target);
  13.   // entity_type, field_name, bundle_name
  14.   foreach ($info['bundles'] as $entity_type => $bundle_name) {
  15.     if (in_array($entity->type, $bundle_name)) {
  16.       $field_entity_type = $entity_type;
  17.       break;
  18.     }
  19.   }
  20.   $field_instance = field_info_instance($field_entity_type,$target,$entity->type);
  21.  
  22.   $target_index = array_shift($info['indexes']);
  23.   $target_index = $target_index[0];
  24.  
  25.   $field = isset($entity->{$target}) ? $entity->{$target} : array();
  26.   $i = isset($field[LANGUAGE_NONE]) ? count($field[LANGUAGE_NONE]) : 0;
  27.   foreach ($value as $v) {
  28.     if (!is_array($v) && !is_object($v) && $v!='') {
  29.       $query = db_select($info['settings']['target_type'], 't')
  30.         ->fields('t');
  31.       // This is a a quick fix to allow us to use multiple entity types.
  32.       switch($info['settings']['target_type']) {
  33.         case 'commerce_product' :
  34.           $field_name = 'sku';
  35.           $entity_identifier = 'product_id';
  36.           $query->condition('sku', $v);
  37.           break;
  38.         case 'node' :
  39.           $field_name = 'title';
  40.           $entity_identifier = 'nid';
  41.           $query->condition('title', '%' . $v .'%', 'LIKE');
  42.       }
  43.       // Find the entity id based on the provided label value.
  44.       $or_condition = db_or();
  45.       // Limit our query to only the target bundles availabe to this field.
  46.       foreach ($info['settings']['handler_settings']['target_bundles'] as $bundle) {
  47.         $or_condition->condition('type', $bundle);
  48.       }
  49.       $query->condition($or_condition);
  50.       $etid = $query->execute()->fetchAssoc();
  51.       if ($etid) {
  52.         $field[LANGUAGE_NONE][$i][$target_index] = $etid[$entity_identifier];
  53.       }
  54.       else {
  55.       }
  56.     }
  57.     if ($info['cardinality'] == 1) {
  58.       break;
  59.     }
  60.     $i++;
  61.   }
  62.   $entity->{$target} = $field;
  63. }

 

3.    Set up your importer to use your custom importer for the multiple value entity reference field.

Hope this helps!

Blog photo provided by Bruce Turner