Friday, 18 July 2014

Field Collection Migration in drupal 7


In this blog,I am explaining the successful method to migrate an xml node into a  drupal 7 multi  valued field collection.I believe this can be useful to all facing issues with field collection migration in drupal 7.

What is a field collection in drupal?

A field collection is internally represented as an entity, which is embedded in the host entity.  It allows a set of fields to be combined together and treated as a single field.  For example, a field collection named Playlist can be created consisting of the text fields Song and Artist. When a Playlist entry is created the Song and Artist fields are presented together as elements of the Playlist field.


Issues in field collection migration

In drupal 7,it is very easy to migrate the entity reference fields from source xml files as it will search the source fields, creates an array and maps to the corresponding field.If the field can have multiple values,array will populate the values in the corresponding fields.But in case of field collection,it is an entity embedded in the host entity.So we should deal with field collection migration in a different way.


Solution which worked for me

I am using migrate contrib module for the migration task.I am going to elaborate with an example code.
Any migration should have a  source,destination and field mapping.
We will begin with an example.

I have a content type “studio” and it has a field collection with field machine name  field_photosin drupal.

field_photos has three fields

field_name (type :text )
field_credit (type :text )
field_attachment(type :entity reference field which has an “image” content type as a target type)



The following code is a sample  node taken from an xml file for the studio content type. We are going to migrate this xml node values to mutli valued field collection “field_photos” in  a content type "studio".



<myphotos_grid grid="true"><![CDATA[<rows>
  <row rowID="1">
    <rowAttribute>
      <param>photo-name</param>
      <value>photname1</value>
    </rowAttribute>
    <rowAttribute>
      <param>photo-credit</param>
      <value>my credit</value>
    </rowAttribute>
    <rowAttribute>
      <param>attachment</param>
      <value>1009343343</value>
    </rowAttribute>
  </row>
  <row rowID="2">
    <rowAttribute>
      <param>photo-name</param>
      <value/>
    </rowAttribute>
    <rowAttribute>
      <param>photo-credit</param>
      <value/>
    </rowAttribute>
    <rowAttribute>
      <param>attachment</param>
      <value>4432434344</value>
    </rowAttribute>
  </row>
</rows>]]></myphotos_grid>


I want to map the photo-name to the field_name,photo-credit to field_credit and attachment to the field_attachment


Generally for the migration of content type studio ,we will write one class extending the xml migration class and will specify the field mappings. After the field mappings are done, override the prepare function.

Function 

prepare($node, stdClass $row) 

$node -   Entity object to build. Prefilled with any fields mapped in the Migration.

$row - Raw source data object - passed through to prepare handlers.



Below is the code used to create field collection for each content after field mappings are done.

 fieldmappings -> prepare -> save



//$node will be the stdClass object for the current  studio(contains the fields done in migration mapping) and $row will be source row object
public function prepare($node, stdClass $row) {
//fetching the field value, assume path to node is //fields/my_photos in the xml file
  $rows_grid = $row->xml->fields->my_photos;
//CDATA function will contain the data not in xml format, load it as xml
  $grid_rows_items = simplexml_load_string($rows_grid);
  $collection_value = array('field_name' => 'field_photos’);
  $counter = -1;
//loop through the grid rows
  foreach($grid_rows_items as $grid_rows_item) {
    $counter++;
    $photo_id_destination = NULL;
//fetching the photo name.
    $photo_name= $grid_rows_item>xpath('rowAttribute[param="photoname"]/value');
//fetching the photo credit
    $photo_credit = $grid_rows_item->xpath('rowAttribute[param="photo-credit"]/value');
//fetching the photo id
    $photo_id = $grid_rows_item->xpath('rowAttribute[param="attachment"]/value');
    if ($slide_item != ''){
   $photo_id = ‘image/’. $photo_id;
  //looking up the migration map ,$this means current pointer (pointer used for field mapping), image xml files will be present inside the image folder
   $photo_id_destination = $this->handleSourceMigration(array(
            'image',
          ), array($photo_id));
     }     
//Use function entity_create to create a field collection entity 
//arg1: The type of the entity.
//arg2: An array of values to set, keyed by property name. If the entity type has bundles the bundle key has to be specified.
  $collection_entity = entity_create('field_collection', $collection_value);
//set the host as the studio content type
  $collection_entity->setHostEntity('node', $node);     
//setting the values
 $collection_entity->field_name[LANGUAGE_NONE][0]['value'] = $photo_name;
 $collection_entity->field_credit[LANGUAGE_NONE][0]['value'] = $photo_credit;
 if (!is_null($photo_id_destination )) {
   $collection_entity->field_attachment[LANGUAGE_NONE][0]['target_id'] = $photo_id_destination ;
 }
 $collection_entity->save(TRUE);
 $node->field_gallery_slides_grid[LANGUAGE_NONE][$counter]['value'] = 
 $collection_entity->item_id;
 $node->field_gallery_slides_grid[LANGUAGE_NONE][$counter]['revision_id'] = 
 $collection_entity->revision_id
}


When in doubt, "whatever works" is the correct standard for migration code. Hope that helps get you started!