Quan ens encarreguen una web en drupal a Omitsis és molt habitual que sigui una web ja existent i que per tant tingui un contingut que vulguin migrar.
A Drupal existeix el mòdul migrate que està incorporat al core i que ens permet migrar contingut. És un mòdul molt potent, versàtil i que facilita fer rollbacks de sèrie. Els rollbacks ens permeten «desimportar», molt útil per quan encara estem desenvolupant i hi ha errors.
El mòdul migrate permet importar des de diferents fonts: un Drupal 7, una base de dades tingui l’estructura que tingui, des d’un csv, un JSON, XML, SOAP, etc.
És molt comú que ens passin un excel amb tots els camps a migrar per cada tipus de contingut. En aquest post explicarem com fer aquest tipus de migració: des d’un csv, ja que tots els fulls de càlcul (excel per exemple) permeten desar com a csv i aquest és un format estàndard més ben suportat.
Tot el que explicarem és vàlid per a Drupal modern. Quan dic “Drupal modern” em refereixo a Drupal 8 i posteriors (9, 10, 11…), perquè a partir del 8 va canviar bastant la cosa amb l’adopció de Symfony i les actualitzacions entre versions van començar a ser més portables. Avui en dia, si estàs començant un projecte, hauries d’estar a Drupal 10 o Drupal 11. Drupal 7, 8 i 9 ja no tenen suport, així que si encara tens alguna cosa allà… bé, ja tens una altra cosa a la llista de tasques.
El primer és instal·lar els mòduls que seran necessaris. El migrate ja està al core de Drupal però necessitarem alguns més: migrate_plus, migrate_tools i migrate_source_csv.
Instal·lem aquests mòduls fent servir composer:
composer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csvI els activem, amb drush seria així:
drush -y en migrate migrate_plus migrate_tools migrate_source_csvAra hem de crear els fitxers yml que definiran les migracions. Això ho hem de fer dins d’un mòdul custom, que podem tenir ja o podem crear-ne un d’específic per al migrate (opció més neta).
Pots generar un mòdul de manera fàcil amb la drush, en el nostre cas el direm custom_migrate.
drush generate moduleAl directori d’aquest mòdul creem una subcarpeta anomenada config i dins d’aquesta una altra anomenada install. Aquí posarem els fitxers yml que definiran les migracions.
La nomenclatura d’aquests fitxers ha de ser:
migrate_plus.migration.[ID_MIGRACIO].ymlPer exemple, si volem importar productes el nom del fitxer podria ser aquest:
migrate_plus.migration.products.ymlEn aquest exemple l’arxiu estaria a la següent ruta:
web/modules/custom/custom_migrate/config/install/migrate_plus.migration.products.ymlEl mòdul migrate_plus ens permet també crear grups, per després fer les importacions de cop (i els rollbacks). Pots veure en aquest post com fer-ho. En aquest exemple no els farem servir, però és bastant pràctic.
Ara necessitem un mòdul més: config_devel. Aquest ens permet que Drupal torni a llegir els fitxers de configuració quan li demanem, sense haver d’estar instal·lant i desinstal·lant el mòdul cada vegada que canviem alguna cosa. Això sí, config_devel és només per a entorns de desenvolupament, no el deixis activat en producció.
Per instal·lar-lo:
composer require --dev drupal/config_develI per activar-lo:
drush -y en config_develAra cal dir-li a Drupal que ha de tenir en compte el fitxer d’importació que hem definit. Això es posa al .info.yml del nostre mòdul. L’arxiu sencer quedaria així:
name: 'Custom migrate'
type: module
description: 'Mòdul d''importacions de contingut'
core_version_requirement: ^10 || ^11
package: 'Custom'
dependencies:
- migrate
- migrate_plus
- migrate_tools
- config_devel
config_devel:
install:
- migrate_plus.migration.products
# - migrate_plus.migration.altra_importacio
Ara ens falta el més important: el contingut del yml d’importació. Això donaria per a molts posts, així que posarem un exemple senzill:
Primer cal definir l’id i un label anirà bé per posar-li un nom bonic.
id: products
label: Import productsDesprés va la secció del source. Aquí cal dir-li quin plugin farem servir i per al cas del csv un path on hi haurà el csv. En aquest cas l’hem posat dins del mòdul per poder-lo integrar al nostre git.
També li hem indicat els ids únics, que al nostre csv s’anomenava «code».
I finalment indiquem els camps del csv.
source:
plugin: 'csv'
# Full path to the file.
path: 'modules/custom/custom_migration/data/products.csv'
# Column delimiter. Comma (,) by default.
delimiter: ','
# Field enclosure. Double quotation marks (") by default.
enclosure: '"'
# The row to be used as the CSV header (indexed from 0),
# or null if there is no header row.
header_offset: 0
# The column(s) to use as a key. Each column specified will
# create an index in the migration table and too many columns
# may throw an index size error.
ids:
- code
# Here we identify the columns of interest in the source file.
# Each numeric key is the 0-based index of the column.
# For each column, the key below is the field name assigned to
# the data on import, to be used in field mappings below.
# The label value is a user-friendly string for display by the
# migration UI.
fields:
0:
name: cat
label: 'Categoria'
1:
name: code
label: 'Product code'
2:
name: title
label: 'Title'
3:
name: image
label: 'Image'Ara la part del process, on indiquem els camps on cal posar les dades, el plugin que farem servir i la font (source).
Si és una cosa molt simple podria ser tan senzill com això:
field_nom_camp_al_nostre_drupal: nom_columna_source
Però moltes vegades no és tan simple. Per exemple ens passen el títol tot en majúscules, així que combinem diversos plugins. També veuràs la part d’importació a un camp media. Això ho explicarem en un altre post aviat.
Un cas real podria ser aquest:
process:
type:
plugin: default_value
default_value: producto
field_cat:
plugin: entity_lookup
source: cat
value_key: name
bundle_key: vid
bundle: cat_producto
entity_type: taxonomy_term
ignore_case: true
field_producto_codigo: code
title:
-
source: title
plugin: callback
callable: mb_strtolower
-
plugin: callback
callable: ucfirst
field_imagen/target_id:
plugin: entity_lookup
value_key: name
source: image
bundle_key: bundle
bundle: image
entity_type: media
ignore_case: 1
access_check: 0Hi ha molts plugins disponibles, els pots veure aquí. A més podem crear plugins de manera molt fàcil.
Ja només falta indicar on anirà tot aquest contingut amb destination. Dependrà de l’entitat que estiguem important, en aquest cas són nodes.
destination:
plugin: entity:nodeI aquí tot sencer:
id: products
label: Import products
source:
plugin: 'csv'
# Full path to the file.
path: 'modules/custom/custom_migration/data/products.csv'
# Column delimiter. Comma (,) by default.
delimiter: ','
# Field enclosure. Double quotation marks (") by default.
enclosure: '"'
# The row to be used as the CSV header (indexed from 0),
# or null if there is no header row.
header_offset: 0
# The column(s) to use as a key. Each column specified will
# create an index in the migration table and too many columns
# may throw an index size error.
ids:
- code
# Here we identify the columns of interest in the source file.
# Each numeric key is the 0-based index of the column.
# For each column, the key below is the field name assigned to
# the data on import, to be used in field mappings below.
# The label value is a user-friendly string for display by the
# migration UI.
fields:
0:
name: cat
label: 'Categoria'
1:
name: code
label: 'Product code'
2:
name: title
label: 'Title'
3:
name: image
label: 'Image'
process:
field_cat:
plugin: entity_lookup
source: cat
value_key: name
bundle_key: vid
bundle: cat_producto
entity_type: taxonomy_term
ignore_case: true
field_producto_codigo: code
title:
-
source: title
plugin: callback
callable: mb_strtolower
-
plugin: callback
callable: ucfirst
type:
plugin: default_value
default_value: producto
field_imagen/target_id:
plugin: entity_lookup
value_key: name
source: image
bundle_key: bundle
bundle: image
entity_type: media
ignore_case: 1
access_check: 0
destination:
plugin: entity:node
migration_dependencies: {}Ara finalment només ens queda executar les comandes des de drush per fer la importació. També es pot fer des de la UI a admin/structure/migrate.
Un apunt abans de seguir: faré servir els alies curts perquè són els que faig servir jo en el dia a dia i els tinc ficats als dits. Si prefereixes la forma llarga, aquí tens l’equivalència:
| Àlies curt | Forma llarga |
|---|---|
drush mim | drush migrate:import |
drush mr | drush migrate:rollback |
drush ms | drush migrate:status |
drush mrs | drush migrate:reset-status |
drush cdi | drush config:devel-import |
drush en | drush pm:install |
El primer és dir-li a drupal que carregui tots els arxius d’importació amb el mòdul config_devel:
drush cdi [nom_del_modul]Que en el nostre cas seria:
drush cdi custom_migrateRecorda que el YAML ha d’estar a config/install i llistat a la secció config_devel: del .info.yml. Si falta qualsevol de les dues coses, drush cdi no el carregarà.
I apunta’t això en algun lloc perquè hi ensopegaràs: cada vegada que toquis el YAML de la migració has de tornar a executar drush cdi. Si no, els canvis no s’apliquen i et tornaràs boig intentant entendre per què la migració continua fent el mateix d’abans. T’ho dic per experiència.
Després podem veure les migracions que tenim i el seu estat amb:
drush msPer fer la importació:
drush mim [id_importacio]Que en el nostre cas seria:
drush mim productsSi alguna cosa surt malament, que el normal és que passi, podem fer un rollback amb aquesta comanda:
drush mr productsTambé és possible que la migració s’encalli, amb la qual cosa primer abans de fer el rollback cal posar-la en idle:
drush mrs productsAlguns flags que val la pena conèixer
Quan portis unes quantes migracions a l’esquena et trobaràs amb que sempre acabes fent servir els mateixos quatre flags. Els poso aquí perquè no els hagis de descobrir a cops com vaig fer jo.
--limit
Per provar la migració amb unes poques files abans de llançar-la sencera. Si el teu CSV té 50.000 files i sospites que alguna cosa fallarà, no t’hi llencis de cap:
drush mim products --limit=10--update
Reimporta els registres que ja estaven importats. Útil quan canvies alguna cosa al process i vols que s’apliqui també al que ja era a la base de dades, sense haver de fer rollback complet:
drush mim products --update--idlist
Per reimportar (o importar per primera vegada) un id concret o diversos. Va perfecte quan estàs depurant un cas estrany i et vols centrar en una fila específica:
drush mim products --idlist=ABC123
drush mim products --idlist=ABC123,DEF456--sync
Sincronitza el destí amb l’origen: importa el nou, actualitza l’existent i esborra del destí el que ja no és a l’origen. Compte amb aquest, perquè si t’equivoques esborrant del CSV et carregues contingut a Drupal. Però si el que vols és just això, t’estalvia molt embolic:
drush mim products --syncI això és més o menys tot. No pretén ser una guia exhaustiva del mòdul migrate, donaria per a moltíssims posts (importar imatges a media, paragraphs, taxonomies amb jerarquia, plugins custom…), però com a introducció per fer una migració des d’un CSV crec que cobreix el bàsic.
Si t’ha servit i t’ha estalviat un parell d’hores de cops de cap contra el teclat, doncs genial. I si ho comparteixes amb algú que estigui començant amb migracions, millor que millor.