I have written an import that uses migration. The import brings in the data correctly, but I can never get prepareRow() to work. It gets called but errors out saying that the fields are undefined. Here is the error:

Undefined property: stdClass::$topicid File /home/tracksadmin/support.trackssoftware.com/html/sites/all/modules/migrate/migrate_hmanual/migrate_hmanual.migrate.inc, line 93

I get that error once for each record imported. I have a very simple prepareRow() here which matches the example code as far as I could tell, but nothing happens.

Here is the code for my import:

class hmanualXMLMigration extends XMLMigration {
  public function __construct() {
    parent::__construct();
    $this->description = t('XML feed from Help and Manual ');

    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'sourcename' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE
        )
      ),
      MigrateDestinationNode::getKeySchema()
    );
        
    $xml_folder = 'xxx';
    $item_url = $xml_folder . ':id';

    $this->source = new MigrateSourceList(
      new MigrateListFiles( array(
           'xxxx'),
           'xxx',
           '/(.*\.xml$)/i'),
      new MigrateItemXML($item_url), 
      $this->fields()
    );

    $this->destination = new MigrateDestinationNode('book');

    $this->addFieldMapping('title', 'topictitle')
         ->xpath('/topic/topictitle');
    $this->addFieldMapping('body', 'topicbody')
        ->arguments(array('summary' => 'test summary', 'format' => 'full_html'))
        ->xpath('/topic/topicbody');
    $this->addFieldMapping('field_help_context', 'helpcontext')
         ->callbacks(array($this, 'fixHelpContact'))
         ->xpath('/topic/helpcontext');
    $this->addFieldMapping('field_topic_id', 'topicid')
         ->xpath('/topic/topicid');
  }
  
  /* Help context is an integer, but it may be blank in the source. In that case we default it to 0 */
  protected function fixHelpContact($help_context) {
    return '0' . $help_context;
  }  
  
  /* Ignore files without a topicid */
  function prepareRow($row) {
      $row->topicid = 'test' . $row->topicid;  // << this is the line it doesn't like.
  }      

  function fields() {
    return array(
      'topicid' => 'Topic ID',
      'helpcontext' => 'Context ID',
      'title' => 'Title',
      'topicbody' => 'Body'
    );
  }
}

Comments

mikeryan’s picture

Category: bug » support
Status: Active » Closed (works as designed)

You need to use prepare() rather than prepareRow(), or pull the values from the XML object yourself. For XML sources, the fields are not populated yet in prepareRow() - the xpaths which pull them out of the XML are attached to the field mappings, which are processed in applyMappings(), which is a later stage.

The "Row" in prepareRow() may be a little misleading, maybe prepareData() would be better - this method is called from a source plugin as soon as the raw data is retrieved, and it gives you an opportunity to deal with it in its raw form before any mappings are applied. For SQL sources, of course, the raw data is already in nice shape, but for XML sources you've only got a SimpleXMLObject at that stage.

JeremyFrench’s picture

You need to use prepare() rather than prepareRow(), or pull the values from the XML object yourself. For XML sources, the fields are not populated yet in prepareRow() - the xpaths which pull them out of the XML are attached to the field mappings, which are processed in applyMappings(), which is a later stage.

This also means that prepareRow()can't be used to override fields for XML sources. Found that out the hard way.

bjalford’s picture

you can access the data by casting to string e.g.

$desc = (String) $row->xml->description;

oddible’s picture

You need to use prepare() rather than prepareRow(), or pull the values from the XML object yourself. For XML sources, the fields are not populated yet in prepareRow() - the xpaths which pull them out of the XML are attached to the field mappings, which are processed in applyMappings(), which is a later stage.

This should really desperately be in the documentation. The number of hours I spent before I found this post.

To clarify, $row is stdClass which could probably be cast to SimpleXMLObject.
$row->xml is a SimpleXMLElement.

eclipsegc’s picture

So, I know I'm late to this party, but since I was looking for the answer to this myself, I thought I'd share my results.

SimpleXMLObject can be rendered via casting it to a string. Likewise it can be set by simply doing an $object = $value; The elements of $current_row->xml are SimpleXMLObjects so you CAN prepare the values here. What follows is something you might do in order to change pubDate in an rss feed into a unix timestamp.


  public function prepareRow($current_row) {
    if (parent::prepareRow($current_row) === FALSE) {
      return FALSE;
    }
    /**
     * @var MigrateXMLFieldMapping $fieldMapping
     */
    foreach ($this->getFieldMappings() as $subfield => $fieldMapping) {
      $source_field = $fieldMapping->getSourceField();
      if ($source_field == 'pubDate') {
        $current_row->xml->$source_field = strtotime((string) $current_row->xml->$source_field);
      }
    }
  }

I hope this helps.

Eclipse

PS: I extracted this code from a more generic solution that I confirmed was working, but I did not confirm this code directly. Nevertheless, something along these lines should basically work.

jlongbottom’s picture

How would you skip certain XML records based on the data, if it is not available in prepareRow()?

UPDATE:

I was able to use xpath to read the XML data, and skip rows based on that.

$vocabulary = $row->xml->xpath('wp:term_taxonomy');
$vocabulary = (string) $vocabulary[0];
if($vocabulary != 'destinations') {
  return FALSE;
}