390 likes | 627 Views
Using the Migrate Module. How to get legacy data into Drupal. Introduction and Overview. Dwayne Bailey Eye Street Solutions dwayne.bailey@eyestreet.com 609-815-0787 dbcollies Migrate module concepts Example Migration Resources Questions. Migrate module concepts. Very flexible
E N D
Using the Migrate Module How to get legacy data into Drupal
Introduction and Overview • Dwayne BaileyEye Street Solutionsdwayne.bailey@eyestreet.com609-815-0787dbcollies • Migrate module concepts • Example Migration • Resources • Questions
Migrate module concepts • Very flexible • But at the cost of custom module development • Allows for migrations to be stopped and restarted • Migrations can be rolled back • Manage deployments from a UI or via drush • Create a module which subclasses DynamicMigration • Add a hook to tell Migrate about the class • Define the source of the migration • Define the destination • Map fields in the source to fields in the destination • How to keep track of what has been migrated • Migrate assumes one row of the source maps to one row of the destination
Migrate hook • Defined in file modulename.migrate.inc function modulename_migrate_api() { $api = array( 'api' => 2, 'migrations' => array( ’Migration1' => array('class_name' =>’MigrationClass1'), ’Migration2' => array('class_name' =>’MigrationClass2’) ’Migration3' => array('class_name' =>’MigrationClass3’) ); return $api; }
DynamicMigration class abstract class BasicAppealMigration extends DynamicMigration { public function __construct() { // Always call the parent constructor first for basic setup parent::__construct(); } } class Migration1 extends BasicAppealMigration { public function __construct() { parent::__construct(); $this->description = t(’A human readable description'); $this->dependencies = array(‘Migration2’, ‘Migration3’);
DynamicMigration class $this->source = new MigrateSourceSQL(…); $this->destination = new MigrateDestinationNode(…); $this->map = new MigrateSQLMap(…); $this->addFieldMapping(…);
Migration Sources • MigrateSourceCSV • MigrateSourceJSON • MigrateSourceMSSQL • MigrateSourceOracle • MigrateSourceSQL • MigrateSourceXML • ???
Migration Destinations • MigrateDestinationComment • MigrateDestinationMenu • MigrateDestinationMenuLinks • MigrateDestinationNode • MigrateDestinationTable • MigrateDestinationTerm • MigrateDestinationUser • ???
Migration Mapping • MigrateSQLMap new MigrateSQLMap($this->machineName, array( 'CATEGORY_ID' => array('type' => 'int', 'not null' => TRUE, 'unsigned' => TRUE, 'description' => 'Category ID', ) ), MigrateDestinationTerm::getKeySchema() );
Field Mapping $this->addFieldMapping(‘destfield’, ‘sourcefield’) ->description(t(‘A description of this mapping’)) ->issueGroup(t(‘A Grouping Label’)) ->issuePriority (MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM) ->issueNumber(770064) ->separator(‘,’) ->sourceMigration(‘someOtherMigration’) ->arguments(array(‘source_type’ => ‘tid’)) ->defaultValue(‘Some Default here’); $this->addFieldMapping(‘destfield2’) ->issueGroup(t(‘No source data available’)); $this->addFieldMapping(NULL, ‘sourcefield2’) ->issueGroup(t(‘Not migrated from source));
Migrate Drush commands migrate-import (mi) Perform one or more migration migrate-reset-status(mrs) Reset a active migration's status migrate-rollback (mr) Roll back the destination objects migrate-status (ms) List all migrations with current status. migrate-stop (mst) Stop an active migration operation migrate-wipe (mw) Delete all nodes from specified content types.
Example Migration • Source: Oracle database • Usable Source: CSV Files • Destination: • Custom Content type • New taxonomy vocabulary • Intermediate database tables
Generic Parent Migration abstract class BasicAppealsMigration extends DynamicMigration { public function __construct() { // Always call the parent constructor first for basic setup parent::__construct(); } }
Taxonomy Term Migration class AppealTermMigration extends BasicAppealsMigration { public function __construct() { parent::__construct(); $this->description = t('Migrate Appeal Categories from the source CSV to taxonomy terms');
Taxonomy Term Migration $this->map = new MigrateSQLMap($this->machineName, array( 'CATEGORY_ID' => array('type' => 'int', 'not null' => TRUE, 'unsigned' => TRUE, 'description' => 'Category ID', ) ), MigrateDestinationTerm::getKeySchema() );
Taxonomy Term Migration $this->source = new MigrateSourceCSV( variable_get('migrate_appeals_categories_csv'), array( array('CATEGORY_ID', 'Category ID'), array('CATEGORY_DESCRIPTION', 'Category Name'), ), array( 'header_rows'=>variable_get ('migrate_appeals_categories_header') ) );
Taxonomy Term Migration $this->destination = new MigrateDestinationTerm('appeals_category'); $this->addFieldMapping('name', 'CATEGORY_DESCRIPTION'); $this->addFieldMapping('description') ->issueGroup(t('DNM')); $this->addFieldMapping('format') ->issueGroup(t('DNM')); $this->addFieldMapping('weight') ->issueGroup(t('DNM')); $this->addFieldMapping('parent') ->issueGroup(t('DNM'));
Appeal Node Migration? • Multiple source rows map to a single destination node • To simplify processing, intermediate database tables are created prior to the final migration
AppealDocument Migration class AppealDocTableMigration extends BasicAppealsMigration { public function __construct() { parent::__construct(); $this->description = 'Miscellaneous table data'; $table_name = 'migrate_appeals_documents';
AppealDocument Migration $this->map = new MigrateSQLMap($this->machineName, array('APPEAL_ID' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, ), 'TYPE' => array( 'type' => 'varchar', 'length' => 1, 'not null' => TRUE, ) ), MigrateDestinationTable::getKeySchema($table_name) );
AppealDocument Migration $this->source = new MigrateSourceCSV( variable_get('migrate_appeals_documents_csv'), array( array('APPEAL_ID', 'Appeal ID'), array('TYPE', 'Appeal Type'), array('DOCUMENT', 'Document'), ), array( 'header_rows' => variable_get ('migrate_appeals_documents_header'), 'embedded_newlines' => TRUE ));
AppealDocument Migration $this->destination = new MigrateDestinationTable($table_name); $this->addFieldMapping('appeal_id', 'APPEAL_ID'); $this->addFieldMapping('appeal_type', 'TYPE'); $this->addFieldMapping('document', 'DOCUMENT');
Other Table Migrations • AppealCategoryTable • AppealNodeTable
AppealNode Migration class AppealNodeMigration extends BasicAppealsMigration { public function __construct() { parent::__construct(); $this->description = t('Appeals'); $this->dependencies = array('AppealTerm', 'AppealNodeTable', 'AppealCategoryTable', 'AppealDocTable');
AppealNode Migration $this->map = new MigrateSQLMap($this->machineName, array( 'appeal_id' => array( 'type' => 'int', 'not null' => TRUE, 'description' => 'Appeal ID.', 'alias' => 'a', ) ), MigrateDestinationNode::getKeySchema() );
AppealNode query $query = db_select('migrate_appeals_node', 'a') ->fields('a’, array('appeal_id', 'title', 'appeal_type', 'applicant_name', 'disaster_number', 'pa_id', 'date_signed', 'dsr', 'report_type')); $query->condition('deleted', 0);
AppealNode Migration $query->leftJoin('migrate_appeals_documents', 'da', "a.appeal_id=da.appeal_id AND da.appeal_type='A'"); $query->leftJoin('migrate_appeals_documents', 'dl', "a.appeal_id=dl.appeal_id AND dl.appeal_type='L'"); $query->leftJoin('migrate_appeals_documents', 'ds', "a.appeal_id=ds.appeal_id AND ds.appeal_type='S'");
AppealNode Migration $query->leftJoin('migrate_appeals_category_map', 'cm', 'a.appeal_id=cm.appeal_id'); $query->groupBy('a.appeal_id'); $query->addExpression('GROUP_CONCAT(CAST(cm.category_id as CHAR))', 'term_ids'); $query->fields('da', array('document')); $query->fields('dl', array('document')); $query->fields('ds', array('document'));
AppealNode Migration $count_query = db_select('migrate_appeals_node', 'a'); $count_query->addExpression('COUNT(appeal_id)', 'cnt'); $count_query->condition('deleted', 0);
AppealNode Migration $this->source = new MigrateSourceSQL( $query, // The base query array(), // Descriptions of fields $count_query, // A query to count the number of rows array('cache_counts' => TRUE) // Arguments );
AppealNode Migration $this->destination = new MigrateDestinationNode('appeal'); $this->addFieldMapping('title', 'title') ->description(t('Mapping project title to node title')); $this->addFieldMapping('field_appeal_type', 'appeal_type'); $this->addFieldMapping('field_applicant_name', 'applicant_name'); $this->addFieldMapping('field_disaster_number', 'disaster_number'); $this->addFieldMapping('pa_id', 'pa_id'); $this->addFieldMapping('field_date_signed', 'date_signed'); $this->addFieldMapping('field_dsr', 'dsr'); $this->addFieldMapping('field_report_type', 'report_type');
AppealNode Migration $this->addFieldMapping('field_analysis', 'document'); $this->addFieldMapping('field_analysis:format')->defaultValue('filtered_html'); $this->addFieldMapping('field_letter', 'dl_document'); $this->addFieldMapping('field_letter:format')->defaultValue('filtered_html'); $this->addFieldMapping('field_summary_brief', 'ds_document'); $this->addFieldMapping('field_summary_brief:format')->defaultValue('filtered_html');
AppealNode Migration $this->addFieldMapping('appeal_categories', 'term_ids') ->separator(',') ->arguments(array('source_type' => 'tid')) ->sourceMigration('AppealTerm'); $this->addFieldMapping('appeal_categories:create_term') ->defaultValue(FALSE); $this->addFieldMapping('appeal_categories:ignore_case') ->defaultValue(FALSE);
AppealNode Migration $this->addFieldMapping('sticky') ->defaultValue(1); $this->addFieldMapping('uid') ->defaultValue(1); $this->addUnmigratedDestinations( array('created', 'changed', 'status', 'promote', 'revision', 'language', 'revision_uid', 'log’, 'tnid’ ) );
Resources • Migrate module: http://drupal.org/project/migrate • Migrate documentation: http://drupal.org/node/415260 • Example code: http://dwayne.thebaileys.name/appeals/migrate_appeals.tar • Slideshow: http://dwayne.thebaileys.name/appeals/migrate.ppsx • Contact info: Dwayne Bailey Eyestreet Solutions Dwayne.bailey@eyestreet.com 609-815-0787