Migrate-module (Drupal)
De Migrate-module doet imports en kan overweg met diverse soorten externe databronnen. De grote kracht van Migrate boven Feeds, is dat Migrate ingewikkelde mappings kan doen. Je kunt PHP en SQL gebruiken bij het configureren van importttaken. De documentatie startpagina zegt zelfs: if you do not have object-oriented programming skills in PHP, this module is not for you. Gelukkig is de werkelijkheid iets minder dramatisch: Diverse van dit soort programmeersels zijn beschikbaar als submodules voor Migrate. Deze module kent uitstekende drush-ondersteuning. Gebruik via Drush is veel sneller dan gebruik via de webinterface.
Dit artikel behandelt ook functionaliteiten van Migrate Extras, waar dat zo uitkomt.
Inleiding
De Migrate-module is een module voor complexe importprocedures. Een migrate-project programmeer je in php als een submodule van de Migrate-module.
Onderdelen
De module bestaat uit de volgende onderdelen:
- Een API voor defineren en beheren van migratieprocessen
- Een verzameling drush-commando's
- Webinterface
- Een voorbeelden-module (migrate_example).
Functionaliteiten
- De mogelijkheid om bestaande functionaliteit uit te breiden of aan te passen
- Ondersteuning voor PDO (PHP Data Objects, DBTNG), XML, CSV, JSON, MySQL, MSSQL, Oracle API, plus uitbreidbaar naar andere soorten bronnen
- Ingebouwde ondersteuning voor nodes, users, taxonomieën, comments en bestanden. Uitbreidbaar naar andere objecten
- Mapping-tables voor de correspondentie tussen bron- en doelobjecten
- Roll-back-mogelijkheid, zodat je gemakkelijk middels trial-&-error dingen kunt uitproberen
- Tools voor beheer van afhankelijkheden tussen objecten
- Automatisch geheugenbeheer
- Framework for logging
- Web-UI geschikt voor zowel ontwikkelaars als behanghebbenden.
Aan de slag
- Wellicht het beste voorbeeld om mee te beginnen: Bar-bones example
- De submodule migrate_example bevat een aantal voorbeelden, te beginnen met beer. Waarschijnlijk ook handig (voor mij althans): De CMU-module reverse engineeren
- Als je een klasse hebt gedefineerd, niet vergeten om 'm te registreren [1]
Waar is de webinterface voor?
De essentie van Migrate zit in php-bestanden. Waar is de webinterface dan voor? Nou:
- Dashboard: Het dashboard biedt een overzicht van lopende processen, groepen, taken en handlers
- Bediening: Mensen die geen gebruik kunnen maken van Drush, kunnen via de webinterface de module starten en stoppen
- Diverse instellingen: Een hoop dingen kun je via de webinterface instellen, waaronder overrulen van mappings en default-waardes. Ik denk dat dat verrekte handig is.
- Instellingen submodules: Dit is bij uitstek de plek voor configuratiepagina's van contributed modules, zoals de database-specificatie voor migrate_d2d.
Drush
Migrate heeft z'n eigen set Drush-commando's, die welliswaar niet kritiek zijn, maar het leven wél opmerkelijk veraangenamen, en migratie-processen flink versnellen. Zie aparte hoofdstuk elders in dit artikel.
Architectuur
Migraties worden gerealiseerd door migratietaken. Deze bevinden zich in afzonderlijke submodules van de Migrate-module.
Migratie-klasses
Programmeren van een migratietaak gebeurt in php en is sterk object-georiënteerd. Het begint met de instantiatie van een afgeleide klasse van de centrale migration
-klasse. In deze afgeleide klasse incorporeer je instanties van vier andere klassen, die hieronder afzonderlijk worden behandeld:
- Source classes - MigrateSource
- Destination classes - MigrateDestination
- Map classes - MigrateMap
- Field mappings - MigrateFieldMapping.
Naast deze vier migratie-klasses heb je nog diverse aanvullende entiteiten, waaronder handler classes. Zie aldaar.
Source classes
De MigrateSource
-klasse zorgt voor representatie van de brondata. De Migration-klasse (de hoofdklasse) itereert door de rijen die door deze klasse worden gegenereert. Dit zijn de source classes [2]:
- MigrateSourceSQL: Indien de bron een database is die door Drupal benaderd kan worden. Voor details: [3]
- MigrateSourceList: Indien data uit verschillende bronnen bij elkaar komt, gebruik dan de MigrateSourceList-bronklasse. Zie [4] voor meer.
- MigrateSourceMultiItems: Voor representatie van complexe data: [5]
- MigrateSourceXML Import van XML-based sources: [6]
- MigrateCourseCSV: Import van CSV-data: [7]
- MigrateListJSON & MigrateItemJSON Voor import van JSON-gebaseerde data: [8]
- MigrateSourceMSSQL: Data uit MS SQL Server peuteren? Dat kan!
- MigrateSourceOracle: Of liever data uit Oracle halen Ook geen probleem.
- MigrateListFile & MigrateItemFile: Toch liever data uit HTML of andersoortige bronnen halen? Ook dat kan.
- MigrateSourceMongoDB: Data mag ook uit MongoDB komen.
- Eigen bronklasse: En mochten de klasses hiervoor niet zijn wat je zoekt, dan kun je altijd nog je eigen klasse defineren.
Destination Classes
De MigrateDestination-klasse en diens subklasse MigrateDestinationEntity Representeren het doelobject van een migratie. De MigrateDestination-klasses roepen daarnaast handlers aan uit subklasses van MigrateDestinationHandler voor hand- en spandiensten. Klasses:
- MigrateDestinationRole
- MigrateDestinationUser
- MigrateDestinationTerm
- MigrateDestinationNode
- MigrateDestinationComment
- MigrateDestinationFile → Afbeeldingen
- MigrateDestinationNodeRevision
- MigrateDestinationTable
De Migrate Extras-module doet hier een duit in het zakje in de vorm van de MigrateDestinationEntityAPI-klasse.
Map classes
De MigrateMap-klasse houdt de correspondenties bij tussen gemigreerde objecten. Da's reuzehandig voor bv. rollback, of het probleem van veranderde ID's (elders behandeld). Wat de documentatie over map classes zegt:
A key feature of the Migrate module is the tracking of relationships between source records and the Drupal objects that have been created from them. This tracking is managed by the map classes - the abstract class MigrateMap and the concrete class MigrateSQLMap. MigrateSQLMap maintains two tables for each defined migration - a map table and a message table. For each source record that is processed by the import function (whether or not it is successfully imported), a row in the migration's map table is added, keyed by the source record's unique ID. If import of the record was successful, the unique ID of the resulting Drupal object is also stored there. Thus, we know what Drupal objects have been created via migration (thus which ones can be rolled back), and when updating imported content we know what source records to update it from. And, critically, when migrating relationships between objects, we can use the map table to rewrite those relationships so they are properly maintained on the Drupal side.
In de praktijk heb je met map classes alleen te maken bij het aanroepen van de migration constructor aan het begin van de migratietaak. Aspecten:
- Machine name van de migratietaak
- Source key schema definition: Defineer het veld of set van velden dat gebruikt kan worden om individuele bronregels te onderscheiden
- Destination key schema definition: In tegenstelling tot de bron, is het schema van het doel bekend. Deze kun je boven water toveren met getKeySchema(), maar ik denk dat het ook handmatig kan
- Connection key
- Options array
Voorbeeld product.inc, onderdeel van de CMU-module:
// 1. MigrateSQLMap ////////////////////////////////////////// // $this->map = new MigrateSQLMap($this->machineName, array( 'nid' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Ubercart node ID', 'alias' => 'ucp', ), ), // 2. MigrateDestinationEntityAPI ////////////////////////////////////////// // MigrateDestinationEntityAPI::getKeySchema('commerce_product') );
Field Mapping
MigrateFieldMapping
zorgt voor de mapping van een bronveld naar een doelveld [9]. Een MigrateFieldMapping-instantie bevat meestal een aantal mappings. Enkele functionaliteiten:
- Recht-toe-recht-aan-mapping
- Conversie van strings naar arrays
- Subfields (bv. afbeeldingen of body)
- sourceMigration: Behouden van de relatie tussen objecten, ook als na migratie de ID's zijn veranderd. Bv. dat Drupal Commerce-orders nog steeds zijn geassociëerd met de juiste accounts, ook al hebben die accounts niet meer de oorspronkelijke UID's
- Deduplication. Bv. import van productern in Drupal Commerce, afkomstig uit Übercart. In deze laatste mogen SKU's hergebruikt worden. In Drupal Commerce niet
- Documenting mappings: Voorzieningen om een zelf-documentered systeem te ontwikkelen.
Aanvullende exotica
Naast bovengenoemde vier klasses, is er nog een ruim assortiment exotica.
Handler classes
Een voorbeeld van zo'n aanvullende klasse, betreft handler classes. Wat de handleiding over handler classes zegt:
Handlers are classes which enable you to add additional behavior to the processing of data being imported. These are usually used to support migration into contributed or custom modules that maintain their own data connected to core entities. Most of the time this will be related to custom field types.
Vermoedelijke voorbeelden van objecten waarvoor je handler classess nodig hebt:
- Drupal Commerce product-types
- Drupal Commerce displays
- Drupal Commerce orders
- Entity-relationship-veld.
Aanvullende methodes
Verder zijn de volgende methodes beschikbaar [10]:
- prepareRow($row)
- prepare($entity, stdClass $row)
- complete($entity, stdClass $row)
- createStub(Migration $migration, array $source_id)
Nog exotischer
En als dat je nog niet exotisch genoeg is, kun je altijd nog je toevlucht nemen tot [11]:
- pre/post Import/Rollback methods
- pre/post Rollback methods per Entity
- prepareKey method.
Drush
Migrate komt met een eigen set van Drush-commando's, die prettig in het gebruik zijn, en migratie-processen flink versnellen.
Waarom migreren via Drush?
Het is doorgaans beter om een import via Drush te doen dan via de webinterface [12]:
- De webinterface gaat uit van een time-out van 25s, dus elke 25s wordt het importproces gestopt en opnieuw gestart. Vooral bij grote migraties geeft dat veel overhead
- Sneller
- Betere debugging-info
- Robuuster want minder points-of-failures.
Alle commando's
migrate-analyze (maz) Analyze the source fields for a migration. migrate-audit (ma) View information on problems in a migration. migrate-register (mreg) Register or re-register any statically defined migrations. migrate-deregister Remove all tracking of a migration migrate-fields-destination (mfd) List the fields available for mapping in a destination. migrate-fields-source (mfs) List the fields available for mapping from a source. migrate-import (mi) Perform one or more migration processes migrate-mappings (mm) View information on all field mappings in a migration. migrate-messages (mmsg) View any messages associated with a migration. migrate-reset-status (mrs) Reset a active migration's status to idle migrate-rollback (mr) Roll back the destination objects from a given migration 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.
Voorbeelden
drush mi CommerceMigrateUbercartProductProduct drush mi b755a3db1User drush mrt CommerceMigrateUbercartCustomerBillingProfile # Stop drush mrs CommerceMigrateUbercartCustomerBillingProfile # Reset: Vaak nodig bij storingen
Migratietaken & webinterface: Voorbeeld
Migrate » Configuration » Helpers
Zoals eerder al genoemd, zijn helpers klasses om nieuwe doelobjecten of -velden te defineren. Het is niet ondenkbaar dat zo'n object meerdere keren gedefineerd wordt. Bv. in twee contributed modules. Op deze pagina kun je conflicterende helpers uitschakelen.
Migrate Extras
Commerce Übercart Migrate (CUM)
Anatomie van een taak
Ter illustratie de taak CommerceMigrateUbercartOrder in detail, onderdeel van de CMU-module:
Rollback
Om een taak opnieuw uit te voeren, zul je vrij snel gebruik moeten maken van rollback:
- Als er '0' staat onder Unprocessed, weigert Migrate de betreffende taak opnieuw uit te voeren
- Ook als je de betreffende data al op een andere manier hebt verwijderd, moet je tóch rollback doen.
Voorbeeld:
drush mr CommerceMigrateUbercartOrder # mr = migrate rollback drush mi CommerceMigrateUbercartOrder # mi = migrate import
MigrateDestinationEntityAPI::getKeySchema
Migrate Extras heeft een API-call genaamd
MigrateDestinationEntityAPI::getKeySchema
die reuzehandig is om het complete doelschema van bv. een Drupal Commerce producttype te regelen. Maar soms is-ie niet zo reuzehandig. Vandaar dit hoofdstuk.
Alternatieve producttypes specificeren?
Deze methode wordt gebruikt in de code van taak CommerceMigrateUbercartProductMigration
van de CMU-module. Deze fetchet het schema van commerce_product(product), terwijl ik vermoedelijk commerce_product(koolborstel) nodig heb. Hoe pas ik dat aan?
Volgens api.drupalcommerce zou MigrateDestinationEntityAPI::getKeySchema('koolborstel') moeten voldoen, maar dat werkt evenmin als bv. MigrateDestinationEntityAPI::getKeySchema('webform')
Voorlopige conclusie (april 2016):
- Hier is geen snelle oplossing voor: Je moet dus werken met een producttype dat product heet. Na de migratie kan dat producttype wellicht hernoemd worden. Ik heb hier geen manier omheen gevonden, en het staat me bij uit de documentatie van CMU dat het per se op deze manier moet
- De handleiding zegt dat je schema's handmatig kunt specificeren, maar ik weet niet of ik daar zo gelukkig van zou worden.
Worden afbeeldingen meegenomen?
Deze method incorporeert ook eventuele productafbeeldingen. Zie afbeelding.
Mapping productreferenties
In de CMU-taak CommerceMigrateUbercartNodeProduct
heb ik doelveld
field_product_referentie Product-referentie (commerce_product_reference)
maar wat voor waarde geef ik dat veld? En om het complexer te maken: De oorspronkelijke Übercart-producten zijn in aantal toegenomen als Drupal Commerce producttypes, ivm. dubbele SKU's (een mooi voorbeeld waar je in Drupal 7 displays voor zou gebruiken), dus misschien moet dat meegenomen worden in de mapping.
- Tabel
commerce_product
bevat producten oftewel producttypes - Tabel
node
bevat nodes oftewel displays oftewel productnodes - Je kunt productnodes zonder productreferentie instantiëren. Alsof producttypes helemaal genegeerd kunnen worden - Nogal verwarrend
- Als een display verwijst naar een productnode, maar je vult ook de display-velden in, dan verschijnen die velden allebei, bv. twee body-velden. De producttype-velden zijn daarbij duidelijk gelinkt en niet gekopiëerd
Voorbeeld
Dit betreft een database met een paar honderd producttypes en één productnode
MigrateDestinationFile - Productafbeeldingen migreren
Migratie van afbeeldingen, bv. Übercart productafbeeldingen naar Drupal Commerce productafbeeldingen, vereist een eigen aanpak met behulp van DestinationClass MigrateDestinationFile. De Migrate-module biedt drie klasses:
- MigrateFileUri: Standaard-klasse, oa. voor afbeeldingen
- MigrateFileBlob: Voor oa. blob's
- MigraterFileFid: Hobbyen met losse fid's
Van deze drie klasses, is MigrateFileUri de meest relevante. De uitleg hieronder heeft betrekking op deze klasse.
Primaire veldwaarde
- De primaire waarde van deze klasse is een Uri of een locale bestandsspecificatie
- De klasse gaat er vanuit, dat het doel een bestand is in de Drupal-bestandsstructuur
Subargumenten
- source_dir: Prefix voor alle bestanden
- destination_dir: Drupal-gedefineerde stream-wrapper, bv. public://. Standaardwaarde: public://
- destination_file: Standaardwaarde: Bestandsnaam bronlocatie
- file_replace: Wat te doen als het doelbestand al bestaat?
- preserve_files: Wat te doen bij rollback?
- urlencode: Wat te doen me speciale tekens indien doellocatie een URL betreft?
Voorbeeld migrate_d2d
De migrate_d2d-module migreert oa. bestanden. Dat geschiedt middels de code hieronder. Behalve toevoeging van commentaar, is deze code niet aangepast. Merk op dat in dit geval bestanden worden gemigreerd, los van eventuele nodes waar die bestanden bijhoren. Dat maakt het voor mij voor migratie van Übercart naar Drupal Commerce, van beperkt nut:
abstract class DrupalFileMigration extends DrupalMigration { public function __construct(array $arguments) { parent::__construct($arguments); // Uitlezen waardes webinterface ////////////////////////////////////////////////////// // if (!empty($arguments['user_migration'])) { $user_migration = $arguments['user_migration']; $this->dependencies[] = $user_migration; } if (empty($arguments['bundle'])) { $arguments['bundle'] = 'file'; } if (empty($arguments['file_class'])) { $arguments['file_class'] = 'MigrateFileUri'; } if (empty($arguments['destination_dir'])) { $arguments['destination_dir'] = 'public://'; } // SourceFields worden uitgebreid nav. input webinterface ///////////////////////////////////////////////////////// // $this->sourceFields += $this->version->getSourceFields('file',$arguments['bundle']); ///////////////////////////////////////////////////////// // 1. Map classes ///////////////////////////////////////////////////////// // // * Allow derived classes to override this definition by setting it before // calling their parent constructor // * Voorbeeld van een (eventueel) handmatig gespecificeerde map // if (!isset($this->map)) { $this->map = new MigrateSQLMap($this->machineName, array( 'fid' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Source file ID', 'alias' => 'f', ), ), MigrateDestinationFile::getKeySchema() ); } ///////////////////////////////////////////////////////// // 2. Bron ///////////////////////////////////////////////////////// // // Bestanden zijn opgeslagen in een bestandssysteem, // niet in een database. Daarom zijn oa. sourceFields // leeg (denk ik) // $this->source = new MigrateSourceSQL($this->query(), $this->sourceFields, NULL, $this->sourceOptions); ///////////////////////////////////////////////////////// // 3. Doel ///////////////////////////////////////////////////////// // // Gebruik MigrateDestinationFile als doel-klasse // $this->destination = new MigrateDestinationFile($arguments['bundle'],$arguments['file_class']); ///////////////////////////////////////////////////////// // 4. Field mappings ///////////////////////////////////////////////////////// // // * Dit zijn een handjevol van de subargumenten van // MigrateDestinationFile // $this->addFieldMapping('destination_dir') ->defaultValue($arguments['destination_dir']); $this->addFieldMapping('source_dir') ->defaultValue($arguments['source_dir']); $this->addFieldMapping('file_replace') ->defaultValue(MigrateFile::FILE_EXISTS_REUSE); $this->addFieldmapping('preserve_files') ->defaultValue(TRUE); // UID ///////////////////////////////////////////////////////// // if (isset($arguments['default_uid'])) { $default_uid = $arguments['default_uid']; } else { $default_uid = 1; } if (isset($user_migration)) { $this->addFieldMapping('uid', 'uid') ->sourceMigration($user_migration) ->defaultValue($default_uid); } else { $this->addFieldMapping('uid') ->defaultValue($default_uid); } // DNM? ///////////////////////////////////////////////////////// // $this->addUnmigratedSources(array('filename', 'filemime', 'filesize', 'urlencode')); } }
Zie ook
- Commerce Migrate Übercart-module
- Migratie Übercart 2.x naar Drupal Commerce 1.x
- Migratie Übercart 2.x naar Drupal Commerce 1.x - Casussen
Bronnen
Algemeen
- https://www.drupal.org/project/migrate - Home page Migrate-module
- http://btmash.com/article/2012-02-16/migrate-more-less-or-how-i-learned-love-and-create-dynamic-migrations -- Enthousiaste & kundige blogposting
- https://www.drupal.org/node/1561820 - Drush-commando's Migrate
Migrate Community Documentation
- Home page
- Migrate module architecture
- Migration classes
- Source classes
- Destination classes
- Map classes
- Field mappings
- Handler classes
- Caveats
- Contributed module support
- Advanced topics
- Cookbook
- Developing wizards for registering migrations
- Drush Migrate commands
- Improving migration performance
- Migrate module FAQ
- Why you should run migrations in Drush rather than the UI
- External references
MigrateDestinationEntityAPI::getKeySchema
- Migrate: MigrateDestinationEntityAPI -> No destinations Fields available - Migrate Extras-issue
- Mapping fails with MigrateDestinationEntityAPI - Migrate Extras-isue
- wrong parameters for MigrateDestinationEntityAPI:getKeySchema() - CMU-issue
- api.drupalecommerce.org » MigrateDestinationEntityAPI - Onderdeel van Migrate Extras
Afbeeldingen migreren
- MigrateDestinationFile -- Startpagina
- Migrating images, setting url, alt, title in prepareRow, image field not created -- Alt-tekst e.d.
- Correct fieldmapping for image fields -- Wat voegt dit toe?
- Migrate images and other fields -- Iemand met een oplossing, geloof ik