
Como comentaba en el artículo anterior, durante estos meses estaré experimentando con migraciones y preparando algunos casos para un futuro libro, eso espero. Durante estos días de confinamiento quiero seguir publicando pequeños artículos para compartir experiencias relacionadas con las migraciones.
En la entrega anterior abordé las migraciones en Drupal desde el punto de vista de una caja de herramientas: un conjunto de recursos básicos que nos ayudan a plantear una migración.
Hay mucha información que procesar y, por supuesto, intervienen más conceptos, técnicas y tácticas a la hora de resolver una migración. Por eso, este mes quiero escribir algo que me permita experimentar con ellas de una forma más práctica.
Foto de Unsplash, de Émile Séguin, @emileseguin
Índice
La API de migraciones de Drupal puede ser una de las más interesantes, pero también una de las más complejas: sus operaciones suelen estar relacionadas con clases y métodos de otras APIs de Drupal, algo que se hace especialmente evidente al depurar. En cualquier caso, como la cantidad de conceptos puede resultar abrumadora, creo que podemos practicar la mecánica de las migraciones mediante un par de ejercicios.
Para este artículo me había propuesto modelar dos procesos de migración diferentes siguiendo una idea que podría resumirse como «primum vivere, deinde philosophari»: primero experimentar y después teorizar. Por eso he decidido organizarlo de una manera particular:
En los siguientes pasos trabajaremos con dos experiencias concretas:
Ambos son, probablemente, de los escenarios más básicos de migración. Por eso recomiendo este artículo a quien quiera iniciarse en su mecánica, como complemento práctico al estudio de las migraciones en Drupal.
Para el primer caso necesitaremos, por una parte, activar el módulo Migrate del core de Drupal y, por otra, descargar e instalar un módulo contribuido con el que gestionar las migraciones.
Entre las opciones disponibles elegiremos migrate_run, que ya mencionamos en la entrega anterior. Podemos entenderlo como una versión ligera de migrate_tools, aunque en realidad es un fork del proyecto. Ambos proporcionan comandos Drush para ejecutar migraciones, por lo que, si tienes migrate_tools instalado, debes desinstalarlo para evitar conflictos con migrate_run.
La primera lección, como detalle curioso, es que ni migrate_plus ni migrate_tools son dependencias obligatorias para ejecutar migraciones en Drupal: podemos implementar migraciones sin tener estos módulos activados en nuestra instalación.
Conviene saber, además, que migrate_run está optimizado para Drush 9 y posteriores. Si utilizas Drush 8, tendrás que recurrir a una versión adaptada, como la Alpha 4, que todavía estaba preparada para Drush 8.
Con Composer y Drush:
composer require drupal/migrate_run
drush pmu migrate_tools # If you need
drush en migrate migrate_run -y
drush cr
Con Drupal Console:
composer require drupal/migrate_run
drupal mou migrate_tools # If you need
drupal moi migrate migrate_run
En la ruta /admin/modules verás lo siguiente:

Crearemos un módulo personalizado para nuestra primera migración:
cd project/web/modules/custom
mkdir migration_basic_module
Después, el archivo migration_basic_module.info.yml, con este contenido:
name: 'Migration Basic Module'
type: module
description: 'Just a basic example of basic migration process.'
package: 'Migrations Examples 2000'
core: 8.x
dependencies:
- drupal:migrate
Crea el archivo de definición de la migración en la ruta /migration_basic_module/migrations/basic_migration_one.yml.
Nuestro archivo declarativo basic_migration_one.yml describe la migración mediante una lista de parámetros y valores en un archivo estático de tipo YAML. Incluiremos los datos embebidos de dos nodos del tipo de contenido «página básica», cargando únicamente dos valores:
*Chiquito de la Calzada fue una figura popular en España, un humorista legendario.
basic_migration_one.yml
id: basic_migration_one
label: 'Custom Basic Migration 2000'
source:
plugin: embedded_data
data_rows:
-
unique_id: 1
page_title: 'Title for migrated node - One'
page_content: 'Lorem fistrum mamaar se calle ustée tiene musho pelo.'
-
unique_id: 2
page_title: 'Title for migrated node - Two'
page_content: 'Se calle ustée caballo blanco caballo negroorl.'
ids:
unique_id:
type: integer
process:
title: article_title
body: article_content
destination:
plugin: 'entity:node'
default_bundle: page
Esta será la estructura del nuevo módulo personalizado del ejemplo de migración básica:
/project/web/modules/custom/
\__migration_basic_module/
\__migration_basic_module.info.yml
\__migrations/
\__basic_migration_one.yml
Activación de todos los módulos necesarios con Drush:
drush pm:enable -y migrate migrate_run migration_basic_module
drush cr
O con Drupal Console:
drupal moi migrate migrate_run migration_basic_module
Para el segundo caso desactivaremos migrate_run, si procede, y activaremos el conjunto de módulos migrate, migrate_plus y migrate_tools. Para tratar los archivos CSV utilizaremos un plugin de origen incluido en el módulo contribuido Migrate Source CSV, migrate_source_csv. En su versión 3.x, este módulo utiliza league/csv para procesar los archivos CSV. Vamos allá, con Composer y Drush:
composer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csv
drush pmu migrate_run # If you need
drush en migrate migrate_plus migrate_tools migrate_source_csv -y
drush cr
Ahora, en la ruta /admin/modules/:

Crearemos otro módulo personalizado para la segunda migración:
cd project/web/modules/custom
mkdir migration_csv_module
Con un nuevo archivo migration_csv_module.info.yml:
name: 'Migration CSV Module'
type: module
description: 'Just a basic example of basic migration process with a CSV source.'
package: 'Migrations Examples 2000'
core: 8.x
dependencies:
- drupal:migrate
- drupal:migrate_tools
- drupal:migrate_plus
En este ejemplo también necesitaremos un archivo declarativo de la migración, como en el caso anterior, pero lo ubicaremos en otro lugar: la ruta /migration_csv_module/config/install/.
La estructura tendrá este aspecto:
/project/web/modules/custom/
\__migration_csv_module/
\__migration_csv_module.info.yml
\__csv/
\_migration_csv_articles.csv
\__config/
\__install/
\__migrate_plus.migration.article_csv_import.yml
Necesitamos un CSV con los datos originales que vamos a migrar. Podemos generarlo fácilmente con herramientas web como Mockaroo, un generador de datos aleatorios bastante bueno. He creado un CSV con los campos id, title, body, tags e image. Los recursos relacionados se mantienen ahora en mis snippets de GitLab.
Este archivo será la fuente de datos del proceso de migración. De momento, crea los directorios del módulo y coloca el CSV en la ruta /csv:

Y ahora, nuestro archivo migrate_plus.migration.article_csv_import.yml. En apartados posteriores explicaremos su construcción y sus secciones:
uuid: 1bcec3e7-0a49-4473-87a2-6dca09b91aba
langcode: en
status: true
dependencies: { }
id: article_csv_import
label: 'Migrating articles'
source:
plugin: csv
path: modules/custom/migration_csv_module/csv/migration_csv_articles.csv
delimiter: ','
enclosure: '"'
header_offset: 0
ids:
- id
fields:
-
name: id
label: 'Unique Id'
-
name: title
label: Title
-
name: body
label: 'Post Body'
-
name: tags
label: 'Taxonomy Tag'
-
name: image
label: 'Image Field'
process:
title: title
body: body
tags: field_tags
image: field_image
type:
plugin: default_value
default_value: article
destination:
plugin: 'entity:node'
Ya tenemos todos los recursos necesarios para crear la nueva migración. Veamos cómo vamos a abordar el proceso.
Describiremos los diferentes enfoques que aplicaremos a nuestros ejemplos para entenderlos mejor.
En el primer caso hemos planteado una migración tan ligera como sea posible: solo dos nodos con dos campos básicos cada uno y los datos embebidos. Lo más sencillo posible.
Además, en las tres fases ETL de la migración —extracción, transformación y carga— utilizaremos plugins que Drupal ya proporciona. No desarrollaremos ningún plugin personalizado. Si todavía no conoces el concepto de plugin de migración, detente un momento y lee la introducción del primer artículo de esta serie.
Para aligerar el ejemplo utilizaremos Migrate Run, esa versión «lite» de Migration Tools. Emplearemos únicamente los comandos básicos, sin opciones ni parámetros complementarios, con el identificador del archivo de migración como argumento.
En esta ejecución quiero probar algo interesante. Como ejecutaremos la segunda migración como configuración, me parece divertido recorrer el camino inverso. Propongo no instalar ni activar el módulo personalizado del CSV: lo dejaremos únicamente como ubicación del archivo de datos.
Vamos a llevar la migración a otro lugar y ejecutarla desde allí. Sorpresa. Visita la ruta /admin/config/development/configuration/single/import de tu instalación Drupal. ¡Nos vemos allí!
drush migrate:status
drush ms
Output from console:
----------------- -------- ------- ---------- ------------- ---------------------
Migration ID Status Total Imported Unprocessed Last Imported
----------------- -------- ------- ---------- ------------- ---------------------
basic_migration_one Idle 2 0 2
----------------- -------- ------- ---------- ------------- ---------------------
drush migrate:import basic_migration_one
drush mi basic_migration_one
Output from console:
----------------- -------- ------- ---------- ------------- -------------------
Migration ID Status Total Imported Unprocessed Last Imported
----------------- -------- ------- ---------- ------------- -------------------
basic_migration_one Idle 2 2 (100%) 0 2020-03-17 23:19:36
----------------- -------- ------- ---------- ------------- -------------------
Al visitar /admin/content verás los dos nodos nuevos:

drush migrate:rollback basic_migration_one
drush mr basic_migration_one
Output from console:
[notice] Rolled back 2 items - done with 'basic_migration_one'

En la ruta /admin/config/development/configuration/single/import importaremos el archivo de definición de nuestra migración personalizada.
Ve a Importar → Elemento individual, selecciona «Migration» como tipo de configuración y pega el contenido del archivo original de la migración:

Pulsa «Importar» y se creará el nuevo objeto en el sistema de configuración.
¿Y ahora?
Con el archivo de migración bajo la gestión de configuración, puedes ejecutar el proceso con las mismas herramientas del caso anterior. Ahora tenemos una nueva migración disponible desde la consola. Compruébalo con drush migrate:status:

Puedes ejecutar la migración con drush migrate-import article_csv_import. Se crearán los nuevos nodos. ¿La limitación? No se migrarán las etiquetas ni las imágenes: una etiqueta es una referencia a entidad y una imagen no es simplemente un enlace, sino un archivo. Ambos tipos necesitan otros plugins; lo veremos en futuras entregas.
Al estar la migración en el sistema de configuración, podemos editarla, importarla y exportarla con las herramientas básicas de Drush. Por ejemplo, probando drush cex:

Como puedes ver, el sistema de configuración ha puesto directamente el nuevo archivo bajo la gestión de Migrate Plus y ha realizado algunas operaciones: renombrarlo con el prefijo migrate_plus.migration o añadir un archivo para el grupo, que sirve para agrupar procesos de migración.
¿Recuerdas el nombre del archivo? Es el mismo que utilizábamos en /config/install: migrate_plus.migration.article_csv_import.yml. Hemos realizado el mismo proceso, pero desde otra dirección. ¿Te impresiona? ¿No? ¿Al menos te parece interesante?
Recuerda también que puedes utilizar drush cim con este archivo de configuración para cargar la migración en otra instalación Drupal, siempre que tenga acceso al CSV como fuente de datos, claro.
Así hemos migrado unos 102 nodos nuevos utilizando dos enfoques y metodologías diferentes. No está mal.
Esto es importante, así que toca repetir la misma canción: hay que conocer el formato de los plugins y el diverso mundo de los plugins de migración existentes.
Cada plugin responde a un tipo de dato, un formato o una fuente concreta. Conviene conocer bien los principales e investigar los que puedas necesitar, porque las migraciones los utilizan intensivamente. Por eso, por ejemplo, no hemos podido migrar los términos de taxonomía ni las imágenes del segundo caso directamente desde el CSV.
Vamos a revisar los plugins que intervienen en estas dos migraciones a partir de sus archivos de definición.
source:
plugin: embedded_data
data_rows:
...
process:
title: creative_title
body: engaging_content
destination:
plugin: 'entity:node'
default_bundle: page
Para extraer los datos utilizamos el plugin Embedded Data, una clase PHP disponible en /web/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php. En su bloque de documentación puedes ver algunas claves de configuración que puedes utilizar en la definición:
*
* Available configuration keys
* - data_rows: The source data array.
* - ids: The unique ID field of the data.
*
Precisamente data_rows e ids son las claves que utilizamos: el array de datos de origen y su identificador único. Puedes leer más sobre la clase EmbeddedDataSource en la documentación de la API de Drupal.org.
Ahora fíjate en el bloque process y busca… ¿dónde está el plugin de procesamiento? Esto es interesante. Normalmente cada correspondencia de campos necesita un plugin de proceso. Con un poco de «azúcar sintáctico», Migrate API permite simplificar la definición: cuando un campo no necesita un tratamiento específico, el mismo plugin puede encargarse de obtener su valor. Además, ese plugin predeterminado puede quedar implícito. Si no lo declaramos, Drupal aplica el plugin de proceso por defecto.
Ese plugin implícito es la clase Get, la solución básica para procesar campos. La encontrarás en /web/core/modules/migrate/src/Plugin/migrate/process/Get.php. Más información sobre Get.php en la API de Drupal.org. En otras palabras, escribir esto:
process:
title: page_title
Equivale a escribir esto otro:
process:
title:
plugin: get
source: page_title
Así la vida resulta un poco más sencilla, ¿verdad? Recuerda: si no declaras un plugin de procesamiento para un campo, Drupal aplicará Get por defecto.
Por último, en el destino utilizamos el plugin de entidad con el parámetro node para crear nodos del tipo page. Esto recurre al plugin de destino Entity.php, una clase abstracta ubicada en web/core/modules/migrate/src/Plugin/migrate/destination/Entity.php, y a su plugin derivado. Puedes leer más sobre los plugins derivados en Drupal, el plugin de destino Entity.php y la clase derivadora de migraciones.
Creo que en este caso revisar los plugins resulta más sencillo e intuitivo:
source:
plugin: csv
...
process:
...
type:
plugin: default_value
default_value: article
destination:
plugin: 'entity:node'
Para el origen utilizamos el plugin CSV del módulo contribuido migrate_source_csv. En el procesamiento usamos Get por defecto y Default Value para el tipo de contenido. En el destino repetimos el plugin del ejemplo anterior: nuevas entidades.
Como has visto, hemos tratado cada proceso de una manera distinta. El primero, con datos embebidos, forma parte del código, sin más particularidades. El segundo se trata como un elemento de configuración del sistema: al situarlo en config/install, la instalación puede crear un objeto de configuración.
En ambos casos escribimos una definición YAML y colocamos el archivo en una ubicación u otra. Pero hay más diferencias. Resumamos las claves:
/admin/config/development/configuration, y utilizar las herramientas de exportación e importación drush cex y drush cim. La migración se sincroniza y su definición se guarda en la base de datos. Esto permite trabajar con las definiciones desde la interfaz de usuario. Toda una sorpresa. dependencies:
enforced:
module:
- my_own_migration_custom_module
8-4-4-4-12. ¡Genera el tuyo! El original utiliza como ejemplo ilustrativo uuid: cacafuti-1a23-2b45-3c67-4d567890a1b2; contiene caracteres no hexadecimales, por lo que debes sustituirlo por un UUID válido.Descarga, prueba y experimenta con los recursos utilizados en el artículo. Los recursos relacionados se mantienen ahora en mis snippets de GitLab.
Recibe artículos sobre Drupal y desarrollo web directamente en tu bandeja de entrada.
Comments
Los comentarios los proporciona Disqus. Solo se cargan si permites la categoría de comentarios en tus preferencias de cookies.