<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Módulos contribuidos on The Russian Lullaby</title><link>https://www.therussianlullaby.dev/es/tags/m%C3%B3dulos-contribuidos/</link><description>Recent content in Módulos contribuidos on The Russian Lullaby</description><generator>Hugo -- gohugo.io</generator><language>es-ES</language><lastBuildDate>Mon, 29 Jun 2020 00:00:00 +0000</lastBuildDate><atom:link href="https://www.therussianlullaby.dev/es/tags/m%C3%B3dulos-contribuidos/index.xml" rel="self" type="application/rss+xml"/><item><title>Migraciones en Drupal (IV): depurar migraciones I</title><link>https://www.therussianlullaby.dev/es/blog/drupal-migrations-four-debugging-migrations-i/</link><pubDate>Mon, 29 Jun 2020 00:00:00 +0000</pubDate><guid>https://www.therussianlullaby.dev/es/blog/drupal-migrations-four-debugging-migrations-i/</guid><enclosure url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_migrations_main.jpg" type="image/jpeg"/><media:content url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_migrations_main.jpg" medium="image" type="image/jpeg"/><media:thumbnail url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_migrations_main.jpg"/><description>Migraciones en Drupal (IV): depurar migraciones I</description><content:encoded><![CDATA[<p>Las migraciones en Drupal, aunque sus definiciones sean lineales, contienen mucha complejidad heredada. La razón es intuitiva: Migrate API ofrece una interfaz de interacción relativamente sencilla a quien desarrolla migraciones, pero durante el proceso colaboran numerosos subsistemas: entidades, base de datos, plugins… Incluso en la migración más sencilla intervienen muchas clases. Si añadimos que una migración suele producir errores hasta que la afinamos, está claro que una de nuestras primeras necesidades será aprender a depurarlas.</p>
<hr>
<p><strong>Foto de Unsplash, de <a href="https://unsplash.com/@epan5">Krzysztof Niewolny, @epan5</a></strong></p>
<hr>
<p><strong>Índice</strong></p>
<span class="post-toc" aria-hidden="true"></span>

<!-- raw HTML omitted -->
<ul>
<li><a href="#1-introducci%C3%B3n">1. Introducción</a></li>
<li><a href="#2-depuraci%C3%B3n-b%C3%A1sica-revisa-tus-archivos">2. Depuración básica: revisa tus archivos</a></li>
<li><a href="#3-depuraci%C3%B3n-intermedia-con-migrate-devel">3. Depuración intermedia con Migrate Devel</a></li>
<li><a href="#4-wq">4. :wq!</a></li>
</ul>
<!-- raw HTML omitted -->
<h2 id="1-introducción">1. Introducción</h2>
<p>Siguiendo los últimos artículos, quiero ampliar la información sobre migraciones en Drupal. He pensado en una pequeña subserie sobre depuración dentro de esta serie principal. Esta es la primera parte: un conjunto de pasos básicos para obtener toda la información disponible de un proceso de migración. Los ejemplos proceden del <a href="https://gitlab.com/davidjguru/drupal-custom-modules-examples/-/tree/master/Migrations/migration_google_sheet">módulo migration_google_sheet de mi cuenta de GitLab</a>.</p>
<h2 id="2-depuración-básica-revisa-tus-archivos">2. Depuración básica: revisa tus archivos</h2>
<p>Empezaremos por una aproximación muy elemental a la detección de errores. Es fundamental reducir las posibles causas abordando la migración de forma iterativa e incremental: avanzaremos paso a paso y ampliaremos progresivamente los datos migrados.</p>
<h3 id="21-revisar-el-archivo-de-definición-de-la-migración">2.1. Revisar el archivo de definición de la migración</h3>
<p>Comenzamos con el paso más intuitivo. Hay errores que están a la vista y, por habituales que sean, no siempre resultan evidentes.</p>
<p>Antes de nada, revisaremos dos cuestiones que aparecen con frecuencia:</p>
<p><strong>Espacios en blanco:</strong> un espacio adicional puede causar problemas en el archivo de definición. Recorre sus líneas para localizar espacios sobrantes.</p>
<p><strong>Errores de sangrado:</strong> el formato YAML organiza los datos mediante claves y valores, con niveles padre e hijo y sangrado de dos espacios en cada nivel del ejemplo. Un sangrado incorrecto puede provocar un error al procesar el archivo. Revisa todos los niveles, igual que has revisado los espacios.</p>
<p>Puedes apoyarte en un validador de sintaxis YAML como <a href="http://www.yamllint.com">www.yamllint.com</a>, aunque también tendrás que comprobar el resultado.</p>
<h3 id="22-revisar-los-registros-de-la-base-de-datos">2.2. Revisar los registros de la base de datos</h3>
<p>En la instalación básica utilizada en el ejemplo, con el perfil estándar, tenemos setenta y tres tablas. Tras activar migrate, migrate_plus, migrate_tools y el módulo personalizado <em>migration_google_sheet_wrong</em>, pasan a ser setenta y cinco. Se han generado dos tablas nuevas:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">cache_migrate
cache_discovery_migration
</code></pre></div><p>Después de ejecutar la migración con identificador <code>taxonomy_google_sheet_wrong</code>, incluida en el módulo personalizado, aparecen otras dos tablas relacionadas con la migración:</p>
<ul>
<li><strong>migrate_map_taxonomy_google_sheet</strong>. Contiene la información sobre el recorrido de cada fila de datos: las migraciones trabajan fila a fila. Migrate API almacena el identificador de origen, el de destino y un hash relacionado con la fila. Estas correspondencias permiten seguir cambios, actualizar información progresivamente y resolver dependencias entre migraciones. Las búsquedas o <em>lookups</em> se apoyan en estos datos. Por ejemplo, para cargar un término de taxonomía puede ser necesario buscar antes su término padre y mantener así la jerarquía. Si, después de lanzar la migración, no encontramos resultados registrados, no se han almacenado los datos y tendremos que depurar el proceso.</li>
<li><strong>migrate_message_taxonomy_google_sheet</strong>. Guarda los mensajes asociados a la ejecución, también organizados por fila. Cada mensaje tiene su propio identificador y está asociado al <code>id_hash</code> de la fila de origen:</li>
</ul>
<p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_two.png" alt="Columnas de la tabla de mensajes de migración" loading="lazy" decoding="async">

</p>
<p>Podemos obtener esta información con Drush: el contenido de la tabla es lo que aparece al ejecutar:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">drush migrate<span style="color:#a61717;background-color:#e3d2d2">:</span>messages id<span style="color:#a61717;background-color:#e3d2d2">-</span>migration
</code></pre></div><p>Es una forma útil de empezar a recopilar información sobre los errores de la migración.</p>
<h3 id="24-recargar-los-objetos-de-configuración">2.4. Recargar los objetos de configuración</h3>
<p>Durante la depuración también necesitamos actualizar los cambios realizados en el objeto de configuración creado a partir del archivo de definición de <code>config/install</code>.</p>
<p>Cada vez que instalamos el módulo se genera un objeto de configuración disponible en Drupal. Mientras depuramos tendremos que modificar el archivo y recargarlo para comprobar el efecto de los cambios. ¿Cómo podemos facilitar este ciclo?</p>
<p>Primero vincularemos el ciclo de vida del objeto de configuración de migración a la instalación de nuestro módulo. Como vimos al tratar las migraciones como configuración, declararemos el módulo personalizado como dependencia forzada:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">dependencies<span style="color:#a61717;background-color:#e3d2d2">:</span>
   enforced<span style="color:#a61717;background-color:#e3d2d2">:</span>
      module<span style="color:#a61717;background-color:#e3d2d2">:</span>
          <span style="color:#a61717;background-color:#e3d2d2">-</span> migration_google_sheet
</code></pre></div><p>Podemos realizar importaciones específicas con Drush o Drupal Console, aprovechando las opciones de importación individual.</p>
<p><strong>Con Drupal Console:</strong></p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">drupal config<span style="color:#a61717;background-color:#e3d2d2">:</span>import<span style="color:#a61717;background-color:#e3d2d2">:</span>single <span style="color:#a61717;background-color:#e3d2d2">--</span>directory=<span style="color:#d14">&#34;/modules/custom/migration_google_sheet/config/install&#34;</span> <span style="color:#a61717;background-color:#e3d2d2">--</span>file=<span style="color:#d14">&#34;migrate_plus.migration.taxonomy_google_sheet.yml&#34;</span>

</code></pre></div><p><strong>Con Drush:</strong></p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">drush cim <span style="color:#a61717;background-color:#e3d2d2">--</span>partial <span style="color:#a61717;background-color:#e3d2d2">--</span>source=<span style="color:#a61717;background-color:#e3d2d2">/</span>folder<span style="color:#a61717;background-color:#e3d2d2">/</span>
</code></pre></div><p>También podemos eliminar objetos de la configuración activa con ambas herramientas:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">drush config<span style="color:#a61717;background-color:#e3d2d2">-</span>delete <span style="color:#d14">&#34;migrate_plus.migration.taxonomy_google_sheet&#34;</span>
drupal config<span style="color:#a61717;background-color:#e3d2d2">:</span>delete active <span style="color:#d14">&#34;migrate_plus.migration.taxonomy_google_sheet&#34;</span>
</code></pre></div><p>Si preferimos la interfaz de Drupal, el módulo contribuido <a href="https://www.drupal.org/project/config_delete">Config Delete</a> incorpora opciones al menú de sincronización para eliminar elementos de configuración. Basta con descargarlo con Composer y activarlo con Drush o Drupal Console:</p>
<pre><code>composer require drupal/config_delete
drush en config_delete -y
</code></pre><p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_eight.png" alt="Acciones del módulo Config Delete" loading="lazy" decoding="async">

</p>
<p>Así podemos volver a importar objetos sin chocar con versiones ya existentes en la base de datos. Si prefieres actualizar y comparar versiones, puedes valorar <a href="https://www.drupal.org/project/config_update">Configuration Update Manager</a>.</p>
<h2 id="3-depuración-intermedia-con-migrate-devel">3. Depuración intermedia con Migrate Devel</h2>
<p>Hemos revisado los datos detenidamente y, aun así, la migración de términos desde Google Spreadsheet no parece funcionar.</p>
<p>Necesitamos técnicas intermedias para obtener más información. En esta fase nos ayudarán módulos y plugins que permiten observar mejor lo que ocurre durante la migración.</p>
<h3 id="31-migrate-devel">3.1. Migrate Devel</h3>
<p><a href="https://www.drupal.org/project/migrate_devel">Migrate Devel</a> es un módulo contribuido que añade funcionalidades mediante opciones nuevas de Drush. Funciona con <em>migrate_tools</em> y <em>migrate_run</em>.</p>
<h5 id="actualización-del-03072020-versión-8x-20-alpha2">Actualización del 03/07/2020: versión 8.x-2.0-alpha2</h5>
<p>Justo cuando publiqué este artículo, <a href="https://twitter.com/MartianWebDev">Andrew Macpherson</a>, nuevo mantenedor de Migrate Devel y uno de los mantenedores de accesibilidad del core de Drupal, dejó un comentario al pie del original con novedades importantes. Desde que empecé el borrador se había publicado una versión nueva, el 28 de junio, compatible con Drush 9, ¡y no lo sabía! Esa versión evita instalar el parche que se describe más abajo.</p>
<p>Para instalarla, la descargamos con Composer y la activamos con Drush: <a href="https://www.drupal.org/project/migrate_devel/releases/8.x-2.0-alpha2">Migrate Devel 8.x-2.0-alpha2</a>.</p>
<pre><code>composer require drupal/migrate_devel
# To install the 2.0 branch of the module:
composer require drupal/migrate_devel:^2.0
drush en migrate_devel -y
</code></pre><h5 id="para-versiones-anteriores-a-8x-20-alpha2">Para versiones anteriores a 8.x-2.0-alpha2</h5>
<p>Si trabajas con versiones anteriores, hay algunas particularidades. Estaban preparadas para Drush 8 y la adaptación a Drush 9 y posteriores aún no parecía cerrada.</p>
<p>En la rama 8.x-1.x había una etiqueta publicada dos semanas antes: <a href="https://git.drupalcode.org/project/migrate_devel/-/tree/8.x-1.4">migrate_devel/tree/8.x-1.4</a>.</p>
<p>Para utilizar esas versiones con Drush 9 o superior, el parche de <a href="https://www.drupal.org/node/2938677">esta incidencia</a> resultaba prácticamente imprescindible. Aunque tampoco parecía definitivo, permitía ejecutar de forma controlada algunas funciones del módulo. Veremos sus aportaciones.</p>
<p>Podemos descargar el parche con wget y aplicarlo con git apply:</p>
<pre><code>cd /web/modules/contrib/migrate_devel/
wget https://www.drupal.org/files/issues/2018-10-08/migrate_devel-drush9-2938677-6.patch
git apply migrate_devel-drush9-2938677-6.patch
</code></pre><p>Otra posibilidad es añadirlo a la sección de parches de <code>composer.json</code>, si tenemos activada la extensión <a href="https://github.com/cweagans/composer-patches">composer-patches</a>.</p>
<p>La instalamos con:</p>
<pre><code>composer require cweagans/composer-patches
</code></pre><p>Y añadimos el parche a la sección <code>extra</code> de <code>composer.json</code>:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_three.png" alt="Incorporación del parche a Composer" loading="lazy" decoding="async">

</p>
<h5 id="cómo-funciona">Cómo funciona</h5>
<p>Al ejecutar una migración con las opciones de Migrate Devel obtenemos valores por consola que podemos inspeccionar. Por ejemplo, con <strong>&ndash;migrate-debug</strong>:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_four.png" alt="Primera parte de la salida de Migrate Devel" loading="lazy" decoding="async">



<img src="/images/post/davidjguru_drupal_migrations_debugging_five.png" alt="Segunda parte de la salida de Migrate Devel" loading="lazy" decoding="async">

</p>
<p>Es una vista parcial del procesamiento de una fila: muestra la fuente, los valores asociados y el identificador final de destino, almacenado en la tabla de correspondencias para seguir el proceso:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_six.png" alt="Salida de Migrate Devel y registro en la base de datos" loading="lazy" decoding="async">

</p>
<p>Vemos que al valor <code>1</code> de origen, en el primer array, se le asignó el identificador <code>117</code> en destino. También será el identificador interno de la nueva entidad de Drupal, aquí un término de taxonomía. Así relacionamos el registro de migración con la entidad creada y almacenada.</p>
<p><strong>¿Y los suscriptores de eventos?</strong> Migrate Devel crea una clase que implementa <code>EventSubscriberInterface</code> y escucha eventos de Migrate API, disponibles en el módulo migrate del core:</p>
<pre><code>Called from +56
/var/www/html/web/modules/contrib/migrate_devel/src/EventSubscriber/MigrationEventSubscriber.php
</code></pre><p>La llamada procede de la clase que escucha estos eventos. En <a href="https://git.drupalcode.org/project/drupal/-/tree/9.0.x/core/modules/migrate/src/Event">modules/migrate/src/Event</a> se definen muchos, pero Migrate Devel presta atención especialmente a dos:</p>
<ol>
<li><a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Event%21MigratePostRowSaveEvent.php/class/MigratePostRowSaveEvent/8.2.x">MigratePostRowSaveEvent.php</a>.</li>
<li><a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Event%21MigratePreRowSaveEvent.php/8.6.x">MigratePreRowSaveEvent.php</a>.</li>
</ol>
<p>Estos corresponden a las dos opciones Drush del módulo, que originalmente acababan llamando a <code>dump()</code> de Kint, proporcionado por Devel, para imprimir los mensajes. En la versión 8.x-2.0-alpha2 se sustituyen esas llamadas por Symfony VarDumper. Donde antes teníamos:</p>
<pre><code>  /**
   * Pre Row Save Function for --migrate-debug-pre.
   *
   * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
   *    Pre-Row-Save Migrate Event.
   */
  public function debugRowPreSave(MigratePreRowSaveEvent $event) {
    $row = $event-&gt;getRow();

    $using_drush = function_exists('drush_get_option');
    if ($using_drush &amp;&amp; drush_get_option('migrate-debug-pre')) {
      // Start with capital letter for variables since this is actually a label.
      $Source = $row-&gt;getSource();
      $Destination = $row-&gt;getDestination();

      // We use kint directly here since we want to support variable naming.
      kint_require();
      \Kint::dump($Source, $Destination);
    }
  }
</code></pre><p>Ahora tenemos:</p>
<pre><code>  /**
   * Pre Row Save Function for --migrate-debug-pre.
   *
   * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
   *    Pre-Row-Save Migrate Event.
   */
  public function debugRowPreSave(MigratePreRowSaveEvent $event) {
    if (PHP_SAPI !== 'cli') {
      return;
    }

    $row = $event-&gt;getRow();

    if (in_array('migrate-debug-pre', \Drush\Drush::config()-&gt;get('runtime.options'))) {
      // Start with capital letter for variables since this is actually a label.
      $Source = $row-&gt;getSource();
      $Destination = $row-&gt;getDestination();

      // Uses Symfony VarDumper.
      // @todo Explore advanced usage of CLI dumper class for nicer output.
      // https://www.drupal.org/project/migrate_devel/issues/3151276
      dump(
        '---------------------------------------------------------------------',
        '|                             $Source                               |',
        '---------------------------------------------------------------------',
        $Source,
        '---------------------------------------------------------------------',
        '|                           $Destination                            |',
        '---------------------------------------------------------------------',
        $Destination
      );
    }
  }
</code></pre><p>Puedes consultar los cambios en <a href="https://git.drupalcode.org/project/migrate_devel/-/blob/8.x-2.0-alpha2/src/EventSubscriber/MigrationEventSubscriber.php">MigrationEventSubscriber.php de 8.x-2.0-alpha2</a>.</p>
<p>También encontrarás más información sobre eventos y suscriptores en el artículo de The Russian Lullaby <a href="https://www.therussianlullaby.dev/blog/building-symfony-events-for-drupal/">Building Symfony events for Drupal</a>.</p>
<h3 id="32-el-plugin-de-proceso-debug">3.2. El plugin de proceso Debug</h3>
<p>Migrate Devel también incorpora un plugin de proceso llamado <code>debug</code>, definido en <code>Debug.php</code>. Lo encontrarás en <code>/web/modules/contrib/migrate_devel/src/Plugin/migrate/process/Debug.php</code>. Su documentación explica su responsabilidad:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml"><span style="color:#a61717;background-color:#e3d2d2">/**</span>
 <span style="color:#a61717;background-color:#e3d2d2">*</span> Debug the process pipeline.
 <span style="color:#a61717;background-color:#e3d2d2">*</span>
 <span style="color:#a61717;background-color:#e3d2d2">*</span> Prints the input value, assuming that you are running the migration from the
 <span style="color:#a61717;background-color:#e3d2d2">*</span> command line, and sends it to the next step in the pipeline unaltered.
 <span style="color:#a61717;background-color:#e3d2d2">*</span>
 <span style="color:#a61717;background-color:#e3d2d2">*</span> Available configuration keys<span style="color:#a61717;background-color:#e3d2d2">:</span>
 <span style="color:#a61717;background-color:#e3d2d2">*</span> <span style="color:#a61717;background-color:#e3d2d2">-</span> label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#a61717;background-color:#e3d2d2">(</span>optional<span style="color:#a61717;background-color:#e3d2d2">)</span> a string to print before the debug output. Include any
 <span style="color:#a61717;background-color:#e3d2d2">*</span>   trailing punctuation or space characters.
 <span style="color:#a61717;background-color:#e3d2d2">*</span> <span style="color:#a61717;background-color:#e3d2d2">-</span> multiple<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#a61717;background-color:#e3d2d2">(</span>optional<span style="color:#a61717;background-color:#e3d2d2">)</span> set to TRUE to ask the next step in the process
 <span style="color:#a61717;background-color:#e3d2d2">*</span>   pipeline to process array values individually, like the multiple_values
 <span style="color:#a61717;background-color:#e3d2d2">*</span>   plugin from the Migrate Plus module.
</code></pre></div><p>El método <code>transform()</code>, heredado de la clase abstracta <code>ProcessPluginBase</code>, no transforma el valor: utiliza <code>print_r</code> de PHP para mostrar por consola tanto valores escalares como arrays.</p>
<p>Podemos incluir el plugin de forma autónoma en la cadena de procesamiento para que imprima resultados al procesar cada fila. En nuestro caso modificaremos el bloque <code>process</code> de la migración de términos para revisar los valores que se están migrando.</p>
<p>Primero cambiaremos la estructura. Como sabemos por las entregas anteriores, esta forma:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">process<span style="color:#a61717;background-color:#e3d2d2">:</span>
 name<span style="color:#a61717;background-color:#e3d2d2">:</span> name
 description<span style="color:#a61717;background-color:#e3d2d2">:</span> description
 path<span style="color:#a61717;background-color:#e3d2d2">:</span> url
 status<span style="color:#a61717;background-color:#e3d2d2">:</span> published
</code></pre></div><p>Utiliza implícitamente el plugin Get.php y equivale a esta otra:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">process<span style="color:#a61717;background-color:#e3d2d2">:</span>
 name<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> name
 description<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> description
 path<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> url
 status<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> published
</code></pre></div><p>Añadimos Debug a la cadena con una etiqueta informativa para cada campo:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">process<span style="color:#a61717;background-color:#e3d2d2">:</span>
 name<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> debug
   label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Processing name field value: &#39;</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> name
 description<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> debug
   label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Processing description field value: &#39;</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> description
 path<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> debug
   label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Processing path field value: &#39;</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> url
 status<span style="color:#a61717;background-color:#e3d2d2">:</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> debug
   label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Processing status field value:  &#39;</span>
   plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> get
   source<span style="color:#a61717;background-color:#e3d2d2">:</span> published
</code></pre></div><p>Después recargamos el objeto de configuración desinstalando e instalando nuestro módulo. Como figura como dependencia, al desinstalarlo también se elimina la configuración de la migración:</p>
<pre><code>drush pmu migration_google_sheet &amp;&amp; drush en migration_google_sheet -y
</code></pre><p>Al ejecutarla de nuevo aparecerá información sobre los valores:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_debugging_seven.png" alt="Información de los valores mostrada por Migrate Devel" loading="lazy" decoding="async">

</p>
<p>Así obtenemos información más detallada de los datos que vamos a migrar. Para escenarios más avanzados podemos combinar argumentos y opciones. Por ejemplo, si queremos revisar un solo elemento:</p>
<pre><code> drush migrate-import --migrate-debug taxonomy_google_sheet --limit=&quot;1 items&quot;
</code></pre><p>Esto combina la salida posterior al almacenamiento —a diferencia de <code>--migrate-debug-pre</code>— con la información del plugin, los valores mostrados por la herramienta de volcado y el identificador final de la única entidad procesada.</p>
<p>Aquí los valores son sencillos y apenas los transformamos: los extraemos y los cargamos en destino. En migraciones posteriores introduciremos tratamientos más complejos y esta información resultará aún más valiosa. Piensa en valores que hay que concatenar, recortar, ampliar o adaptar: si mostramos el resultado en cada paso podremos observar mejor toda la secuencia de transformación.</p>
<p>Puedes revisar <a href="https://git.drupalcode.org/project/migrate_devel/-/blob/8.x-2.x/src/Plugin/migrate/process/Debug.php">el código de Debug.php</a> y <a href="https://www.drupal.org/node/3021648">la incidencia de Drupal.org en la que surgió la propuesta de este plugin</a>.</p>
<p>Con esta aproximación empezamos la subserie de depuración de migraciones. ¡Pronto, más experiencias!</p>
<h2 id="4-wq">4. :wq!</h2>
<h5 id="canción-recomendada">Canción recomendada</h5>


<div class="embed-responsive embed-responsive-16by9">
  <iframe
    class="embed-responsive-item"
    src="https://www.youtube-nocookie.com/embed/Z2tvlp7RnlM"
    title="YouTube video"
    loading="lazy"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowfullscreen></iframe>
</div>

]]></content:encoded></item><item><title>Migraciones en Drupal (III): migrar desde Google Spreadsheet</title><link>https://www.therussianlullaby.dev/es/blog/drupal-migrations-three-migrating-from-google-spreadsheet/</link><pubDate>Wed, 27 May 2020 00:00:00 +0000</pubDate><guid>https://www.therussianlullaby.dev/es/blog/drupal-migrations-three-migrating-from-google-spreadsheet/</guid><enclosure url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_main.jpg" type="image/jpeg"/><media:content url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_main.jpg" medium="image" type="image/jpeg"/><media:thumbnail url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_migrations_debugging_main.jpg"/><description>Migraciones en Drupal (III): migrar desde Google Spreadsheet</description><content:encoded><![CDATA[<p>Los sistemas y subsistemas relacionados con la API de migraciones de Drupal resultan apasionantes. En las entregas anteriores quise trazar un mapa lo más completo posible de recursos, posibilidades y personas de referencia en la primera parte, y explicar algunos mecanismos básicos de las migraciones en la segunda. Sabiendo que todo ello abre la puerta a miles de opciones, posibilidades y técnicas, no quería dejar pasar una tercera entrega sin compartir experiencias migrando datos desde un formato tan habitual como una hoja de cálculo de Google: una de las formas en que solemos recibir los datos.</p>
<hr>
<p><strong>Foto de Unsplash, de <a href="https://unsplash.com/@zhenhappy">Pan Xiaozhen, @zhenhappy</a></strong></p>
<hr>
<p><strong>Índice</strong></p>
<span class="post-toc" aria-hidden="true"></span>

<!-- raw HTML omitted -->
<ul>
<li><a href="#1-introducci%C3%B3n-recordemos-los-procesos-etl">1. Introducción: recordemos los procesos ETL</a></li>
<li><a href="#2-exponer-datos-mediante-google-spreadsheet">2. Exponer datos mediante Google Spreadsheet</a></li>
<li><a href="#3-particularidades-de-la-transformaci%C3%B3n-a-json">3. Particularidades de la transformación a JSON</a></li>
<li><a href="#4-caracter%C3%ADsticas-de-la-migraci%C3%B3n">4. Características de la migración</a></li>
<li><a href="#5-m%C3%B3dulo-personalizado-para-la-migraci%C3%B3n">5. Módulo personalizado para la migración</a></li>
<li><a href="#6-wq">6. :wq!</a></li>
</ul>
<!-- raw HTML omitted -->
<h2 id="1-introducción-recordemos-los-procesos-etl">1. Introducción: recordemos los procesos ETL</h2>
<p>Empecemos planteando un escenario: tenemos que migrar una lista de términos de taxonomía a nuestra instalación Drupal. El origen de los datos importa, porque determina qué plugins utilizaremos para la extracción y puede influir también en el procesamiento y en la carga final en el destino.</p>
<p>En este artículo imaginaremos que hemos «heredado» una tarea de migración que había iniciado otra persona. Consiste en poblar con términos de taxonomía un vocabulario ya existente en Drupal. Para practicar con otros plugins y modelos de migración, utilizaremos una hoja de Google como fuente. No olvidemos el marco en el que trabajamos:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_ETL_scheme.png" alt="Esquema ETL y visión general de Drupal Migrate API" loading="lazy" decoding="async">

</p>
<p>Nuestro objetivo es rellenar los campos de un término de taxonomía con los datos de una hoja externa de Google. Estos son los campos que debemos completar:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_taxonomy_term_fields.png" alt="Campos básicos de un término de taxonomía" loading="lazy" decoding="async">

</p>
<p>Lo haremos describiendo un proceso Migrate y ejecutándolo como configuración. ¿Conoces las diferencias entre migraciones como código y como configuración? Encontrarás algunas claves <a href="/es/blog/drupal-migrations-two-examples/#migraciones-como-c%C3%B3digo-o-como-configuraci%C3%B3n">en el artículo anterior de la serie</a>.</p>
<h2 id="2-exponer-datos-mediante-google-spreadsheet">2. Exponer datos mediante Google Spreadsheet</h2>
<p>Para extraer los datos necesitamos una hoja cuyos valores estén expuestos. He creado <a href="https://docs.google.com/spreadsheets/d/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/edit?usp=sharing">esta hoja de cálculo de Google</a>, con columnas que corresponden a los campos de un término de taxonomía y están preparadas para la migración.</p>
<p>Para procesar la fuente utilizaremos el <a href="https://www.drupal.org/project/migrate_google_sheets">módulo contribuido Migrate Google Sheets</a>. Descárgalo con Composer y actívalo:</p>
<pre><code>composer require drupal/migrate_plus #If applicable
composer require drupal/migrate_google_sheets
drush en migrate_plus migrate_google_sheet -y
</code></pre><p>Este módulo trata el recurso como un archivo JSON, pero antes tenemos que realizar algunas tareas: exponer la hoja como fuente JSON utilizando las herramientas de Google.</p>
<ul>
<li>Primero extraemos el <strong>workbook-id</strong> de la dirección de la hoja: <code>docs.google.com/spreadsheets/d/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/</code>. En este caso, <strong>workbook-id: <code>1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM</code></strong>.</li>
<li>También necesitamos el <strong>worksheet-index</strong>, el índice de la pestaña que contiene los datos. Aquí es <strong>1</strong>.</li>
<li>Construimos la URL JSON con el patrón <strong><code>spreadsheets.google.com/feeds/list/[workbook-id]/[worksheet-index]/public/values?alt=json</code></strong>. Para nuestro ejemplo: <a href="http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json">URL de los valores en JSON</a>.</li>
</ul>
<p><strong>¡Eso es todo!</strong> La hoja queda expuesta como un archivo JSON y podemos obtener los valores mediante los plugins de origen elegidos para Migrate API.</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_Google_Spreadsheet_source.png" alt="Datos de Google Spreadsheet en formato JSON" loading="lazy" decoding="async">

</p>
<h2 id="3-particularidades-de-la-transformación-a-json">3. Particularidades de la transformación a JSON</h2>
<p>Ahora podemos ver la fuente JSON en el navegador. Puede resultar útil una extensión que presente los datos con formato, como hace Firefox de forma predeterminada:</p>
<pre><code>      {
        &quot;gsx$id&quot;: {
          &quot;$t&quot;: &quot;1&quot;
        },
        &quot;gsx$name&quot;: {
          &quot;$t&quot;: &quot;Term 1&quot;
        },
        &quot;gsx$description&quot;: {
          &quot;$t&quot;: &quot;Descrip 1&quot;
        },
        &quot;gsx$url&quot;: {
          &quot;$t&quot;: &quot;/t1&quot;
        }
        &quot;gsx$published&quot;: {
          &quot;$t&quot;: &quot;1&quot;
        }
      }
</code></pre><p>Algunos elementos han cambiado respecto al formato original. Fíjate en la comparación:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_google_spreadsheet_json.png" alt="Comparación de los formatos de datos" loading="lazy" decoding="async">

</p>
<p>La transformación a JSON introduce cambios respecto a la hoja. Por ejemplo, Google modifica los identificadores de los campos añadiendo prefijos:</p>
<ul>
<li><code>gsx$</code> para los nombres de campo.</li>
<li><code>$t</code> para los valores almacenados en los campos.</li>
</ul>
<p>Las cabeceras cambian así:</p>
<ol>
<li><code>Id</code> pasa a ser <code>gsx$id</code>.</li>
<li><code>Name</code> pasa a ser <code>gsx$name</code>.</li>
<li><code>Description</code> pasa a ser <code>gsx$description</code>.</li>
<li><code>Url</code> pasa a ser <code>gsx$url</code>.</li>
<li><code>Published</code> pasa a ser <code>gsx$published</code>.</li>
</ol>
<p>Como estos cambios son estables, el plugin se encarga de procesarlos:</p>
<pre><code>// Class GoogleSheets.php
// Module migrate_google_sheets
// @see https://git.drupalcode.org/project/migrate_google_sheets/

// Actual values are stored in gsx$&lt;field&gt;['$t'].
$this-&gt;currentItem[$field_name] = $current['gsx$' . $selector]['$t'];
</code></pre><p>Esto nos permite utilizar los nombres de los datos en su forma «plana»: en minúsculas y sin espacios ni caracteres especiales. Además, las filas se encuentran en la ruta <code>feed/entry</code>, que el módulo también resuelve:</p>
<pre><code>// For Google Sheets, the actual row data lives under feed-&gt;entry.
if (isset($array['feed']) &amp;&amp; isset($array['feed']['entry'])) {
 $array = $array['feed']['entry'];
}
</code></pre><h2 id="4-características-de-la-migración">4. Características de la migración</h2>
<p>La primera observación es que el plugin de Google Spreadsheet extiende el analizador JSON, como vemos en la clase:</p>
<pre><code>use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate_plus\data_parser\JSON;
use GuzzleHttp\Exception\RequestException;

/**
 * Obtain Google Sheet data for migration.
 *
 * @DataParser(
 *   id = &quot;google_sheets&quot;,
 *   title = @Translation(&quot;Google Sheets&quot;)
 * )
 */
class GoogleSheets extends JSON implements ContainerFactoryPluginInterface {
...
}
</code></pre><p>Su bloque de anotaciones lo identifica como un <strong>Data Parser</strong>, así que necesitamos más piezas: un plugin de origen y un <strong>Data Fetcher</strong>. El plugin de Google Spreadsheet será la tercera pieza del proceso.</p>
<p>¿Qué plugins de origen tenemos disponibles en nuestra instalación Drupal? Vamos a consultarlos:</p>
<pre><code>drupal debug:plugin migrate.source
</code></pre><p>La consola mostrará:</p>
<pre><code>drupal@migrations-web:/var/www/html$ drupal debug:plugin migrate.source
 --------------- ---------------------------------------------------------
  Plugin ID       Plugin class
 --------------- ---------------------------------------------------------
  embedded_data   Drupal\migrate\Plugin\migrate\source\EmbeddedDataSource
  empty           Drupal\migrate\Plugin\migrate\source\EmptySource
  url             Drupal\migrate_plus\Plugin\migrate\source\Url
 --------------- ---------------------------------------------------------

drupal@migrations-web:/var/www/html$
</code></pre><p>Podemos utilizar la clase Url.php como plugin de origen, ya que el archivo se expone mediante una URL. En <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate/source/Url.php">la clase Url.php</a> vemos que necesita un analizador de datos; en nuestro caso será la clase de Google Spreadsheet:</p>
<pre><code>  /**
   * The data parser plugin.
   *
   * @var \Drupal\migrate_plus\DataParserPluginInterface
   */
  protected $dataParserPlugin;
</code></pre><p>Al buscar un componente que recupere los datos encontramos <a href="https://git.drupalcode.org/project/migrate_plus/-/blob/8.x-5.x/src/Plugin/migrate_plus/data_fetcher/Http.php">la clase Http.php</a>, preparada para trabajar con el plugin de origen URL mediante HTTP:</p>
<pre><code>/**
 * Retrieve data over an HTTP connection for migration.
 *
 * Example:
 *
 * @code
 * source:
 *   plugin: url
 *   data_fetcher_plugin: http
 *   headers:
 *     Accept: application/json
 *     User-Agent: Internet Explorer 6
 *     Authorization-Key: secret
 *     Arbitrary-Header: foobarbaz
 * @endcode
 *
 * @DataFetcher(
 *   id = &quot;http&quot;,
 *   title = @Translation(&quot;HTTP&quot;)
 * )
 */
class Http extends DataFetcherPluginBase implements ContainerFactoryPluginInterface {
</code></pre><p>Por otra parte, el constructor de Url.php recibe las direcciones de origen. Podemos proporcionar una URL o un conjunto de ellas:</p>
<pre><code>  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
    if (!is_array($configuration['urls'])) {
      $configuration['urls'] = [$configuration['urls']];
    }
    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);

    $this-&gt;sourceUrls = $configuration['urls'];
  }
</code></pre><p>Al reunir las piezas, debemos darles una estructura según su función y posición. Nuestro bloque de origen quedará así:</p>
<pre><code>source:
  plugin: url
  data_fetcher_plugin: http
  data_parser_plugin: google_sheets
  urls: 'http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json'
</code></pre><h2 id="5-módulo-personalizado-para-la-migración">5. Módulo personalizado para la migración</h2>
<p>Crearemos un módulo llamado <code>migration_google_sheet</code> con esta estructura:</p>
<pre><code>/project/web/modules/custom/
                     \__migration_google_sheet/
                         \__migration_google_sheet.info.yml
                           \__config/
                                \__install/
                                     \__migrate_plus.migration.migration.taxonomy_google_sheet.yml
</code></pre><p>Archivo de definición de la migración: <code>migrate_plus.migration.taxonomy_google_sheet.yml</code>.</p>
<pre><code>langcode: en
status: true
dependencies:
  enforced:
    module:
      - migration_google_sheet
id: taxonomy_google_sheet
label: 'Migrating Taxonomy'
source:
  plugin: url
  data_fetcher_plugin: http
  data_parser_plugin: google_sheets
  urls: 'http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json'
  fields:
    - name: id
      label: 'Id'
      selector: id
    - name: name
      label: 'Name'
      selector: name
    - name: description
      label: 'Description'
      selector: description
    - name: url
      label: 'Url'
      selector: url
    - name: published
      label: 'Published'
      selector: published
  ids:
    id:
      type: integer
process:
  name: name
  description: description
  path: url
  status: published
destination:
  plugin: 'entity:taxonomy_term'
  default_bundle: tags
</code></pre><p>Ejecutamos la migración desde la consola:</p>
<pre><code>drush en migration_google_sheet -y
drush migrate:import taxonomy_google_sheet

[notice] Processed 30 items (30 created, 0 updated, 0 failed, 0 ignored) - done with 'taxonomy_google_sheet'
</code></pre><p>¡Hecho! Hemos migrado treinta términos de taxonomía desde una hoja de Google:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_migrating_taxonomy_terms.png" alt="Términos de taxonomía recién migrados" loading="lazy" decoding="async">

</p>
<p>Puedes descargar o clonar el módulo personalizado <a href="https://gitlab.com/davidjguru/drupal-custom-modules-examples/-/tree/master/Migrations">desde mi repositorio de GitLab</a>.</p>
<h2 id="6-wq">6. :wq!</h2>
<h5 id="canción-recomendada">Canción recomendada</h5>


<div class="embed-responsive embed-responsive-16by9">
  <iframe
    class="embed-responsive-item"
    src="https://www.youtube-nocookie.com/embed/je0lFe0MHjU"
    title="YouTube video"
    loading="lazy"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowfullscreen></iframe>
</div>

]]></content:encoded></item><item><title>Migraciones en Drupal (II): ejemplos</title><link>https://www.therussianlullaby.dev/es/blog/drupal-migrations-two-examples/</link><pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.therussianlullaby.dev/es/blog/drupal-migrations-two-examples/</guid><enclosure url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_thinking_about_drupal_migrations_examples_main.jpg" type="image/jpeg"/><media:content url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_thinking_about_drupal_migrations_examples_main.jpg" medium="image" type="image/jpeg"/><media:thumbnail url="https://www.therussianlullaby.dev/images/post/davidjguru_drupal_thinking_about_drupal_migrations_examples_main.jpg"/><description>Migraciones en Drupal (II): ejemplos con datos embebidos y archivos CSV, plugins y gestión como código o configuración.</description><content:encoded><![CDATA[<p>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.</p>
<p>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.</p>
<p>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.</p>
<hr>
<p><strong>Foto de Unsplash, de <a href="https://unsplash.com/@emileseguin">Émile Séguin, @emileseguin</a></strong></p>
<p><strong>Índice</strong></p>
<span class="post-toc" aria-hidden="true"></span>

<!-- raw HTML omitted -->
<ul>
<li><a href="#1-introducci%C3%B3n">1. Introducción</a></li>
<li><a href="#2-preparativos">2. Preparativos</a></li>
<li><a href="#3-enfoques">3. Enfoques</a></li>
<li><a href="#4-migraciones">4. Migraciones</a></li>
<li><a href="#5-conceptos-clave">5. Conceptos clave</a></li>
<li><a href="#6-recursos">6. Recursos</a></li>
<li><a href="#7-wq">7. :wq!</a></li>
</ul>
<!-- raw HTML omitted -->
<h2 id="1-introducción">1. Introducción</h2>
<p><a href="https://www.drupal.org/docs/8/api/migrate-api">La API de migraciones de Drupal</a> 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.</p>
<p>Para este artículo me había propuesto modelar dos procesos de migración diferentes siguiendo una idea que podría resumirse como <strong>«primum vivere, deinde philosophari»</strong>: primero experimentar y después teorizar. Por eso he decidido organizarlo de una manera particular:</p>
<ul>
<li>Los dos procesos se dividen en apartados comunes. En lugar de terminar uno y empezar el siguiente, avanzaremos con ambos en paralelo: tú eliges tu propia aventura.</li>
<li>Solo al final del artículo encontrarás algunos de los conceptos clave que utilizaremos. Primero vamos a jugar con las estructuras y después las entenderemos.</li>
</ul>
<p>En los siguientes pasos trabajaremos con dos experiencias concretas:</p>
<ol>
<li><strong>Migrar datos desde un formato embebido</strong>, quizá el ejemplo más sencillo de una migración en Drupal.</li>
<li><strong>Migrar datos desde un archivo CSV</strong>, un caso algo más complejo que el anterior.</li>
</ol>
<p>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.</p>
<h2 id="2-preparativos">2. Preparativos</h2>
<h3 id="primer-caso-migrar-datos-embebidos">Primer caso: migrar datos embebidos</h3>
<p>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.</p>
<p>Entre las opciones disponibles elegiremos <a href="https://www.drupal.org/project/migrate_run">migrate_run</a>, 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.</p>
<p>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.</p>
<p>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, <a href="https://www.drupal.org/project/migrate_run/releases/8.x-1.0-alpha4">como la Alpha 4, que todavía estaba preparada para Drush 8</a>.</p>
<p><strong>Con Composer y Drush:</strong></p>
<pre><code>composer require drupal/migrate_run
drush pmu migrate_tools # If you need
drush en migrate migrate_run -y
drush cr
</code></pre><p><strong>Con Drupal Console:</strong></p>
<pre><code>composer require drupal/migrate_run
drupal mou migrate_tools # If you need
drupal moi migrate migrate_run
</code></pre><p>En la ruta <code>/admin/modules</code> verás lo siguiente:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_examples_modules.png" alt="Activación de los módulos Migrate y Migrate Run" loading="lazy" decoding="async">

</p>
<h4 id="construcción-de-los-recursos">Construcción de los recursos</h4>
<p>Crearemos un módulo personalizado para nuestra primera migración:</p>
<pre><code>cd project/web/modules/custom
mkdir migration_basic_module
</code></pre><p>Después, el archivo <code>migration_basic_module.info.yml</code>, con este contenido:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">name<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Migration Basic Module&#39;</span>
type<span style="color:#a61717;background-color:#e3d2d2">:</span> module
description<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Just a basic example of basic migration process.&#39;</span>
package<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Migrations Examples 2000&#39;</span>
core<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">8</span>.x
dependencies<span style="color:#a61717;background-color:#e3d2d2">:</span>
  <span style="color:#a61717;background-color:#e3d2d2">-</span> drupal<span style="color:#a61717;background-color:#e3d2d2">:</span>migrate
</code></pre></div><p>Crea el archivo de definición de la migración en la ruta <code>/migration_basic_module/migrations/basic_migration_one.yml</code>.</p>
<p>Nuestro archivo declarativo <code>basic_migration_one.yml</code> 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:</p>
<ol>
<li>Un título: una cadena de texto.</li>
<li>Un cuerpo: un texto basado en el generador ChiquitoIpsum*, <a href="http://www.chiquitoipsum.com">http://www.chiquitoipsum.com</a>.</li>
</ol>
<p>*<a href="https://en.wikipedia.org/wiki/Chiquito_de_la_Calzada">Chiquito de la Calzada</a> fue una figura popular en España, un humorista legendario.</p>
<p><strong>basic_migration_one.yml</strong></p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">id<span style="color:#a61717;background-color:#e3d2d2">:</span> basic_migration_one
label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Custom Basic Migration 2000&#39;</span>
source<span style="color:#a61717;background-color:#e3d2d2">:</span>
  plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> embedded_data
  data_rows<span style="color:#a61717;background-color:#e3d2d2">:</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      unique_id<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">1</span>
      page_title<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Title for migrated node - One&#39;</span>
      page_content<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Lorem fistrum mamaar se calle ustée tiene musho pelo.&#39;</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      unique_id<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">2</span>
      page_title<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Title for migrated node - Two&#39;</span>
      page_content<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Se calle ustée caballo blanco caballo negroorl.&#39;</span>
  ids<span style="color:#a61717;background-color:#e3d2d2">:</span>
    unique_id<span style="color:#a61717;background-color:#e3d2d2">:</span>
      type<span style="color:#a61717;background-color:#e3d2d2">:</span> integer
process<span style="color:#a61717;background-color:#e3d2d2">:</span>
  title<span style="color:#a61717;background-color:#e3d2d2">:</span> article_title
  body<span style="color:#a61717;background-color:#e3d2d2">:</span> article_content
destination<span style="color:#a61717;background-color:#e3d2d2">:</span>
  plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;entity:node&#39;</span>
  default_bundle<span style="color:#a61717;background-color:#e3d2d2">:</span> page
</code></pre></div><p>Esta será la estructura del nuevo módulo personalizado del ejemplo de migración básica:</p>
<pre><code>/project/web/modules/custom/
                     \__migration_basic_module/
                         \__migration_basic_module.info.yml
                             \__migrations/
                                 \__basic_migration_one.yml
</code></pre><p><strong>Activación de todos los módulos necesarios con Drush:</strong></p>
<pre><code>drush pm:enable -y migrate migrate_run migration_basic_module
drush cr
</code></pre><p><strong>O con Drupal Console:</strong></p>
<pre><code>drupal moi migrate migrate_run migration_basic_module
</code></pre><h3 id="segundo-caso-migrar-desde-archivos-csv">Segundo caso: migrar desde archivos CSV</h3>
<p>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, <a href="https://www.drupal.org/project/migrate_source_csv">migrate_source_csv</a>. En su versión 3.x, este módulo utiliza league/csv para procesar los archivos CSV. Vamos allá, con Composer y Drush:</p>
<pre><code>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
</code></pre><p>Ahora, en la ruta <code>/admin/modules/</code>:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_csv_source_modules.png" alt="Activación de Migrate, Migrate Plus y Migrate Tools" loading="lazy" decoding="async">

</p>
<h4 id="construcción-de-los-recursos-1">Construcción de los recursos</h4>
<p>Crearemos otro módulo personalizado para la segunda migración:</p>
<pre><code>cd project/web/modules/custom
mkdir migration_csv_module
</code></pre><p>Con un nuevo archivo <code>migration_csv_module.info.yml</code>:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">name<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Migration CSV Module&#39;</span>
type<span style="color:#a61717;background-color:#e3d2d2">:</span> module
description<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Just a basic example of basic migration process with a CSV source.&#39;</span>
package<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Migrations Examples 2000&#39;</span>
core<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">8</span>.x
dependencies<span style="color:#a61717;background-color:#e3d2d2">:</span>
  <span style="color:#a61717;background-color:#e3d2d2">-</span> drupal<span style="color:#a61717;background-color:#e3d2d2">:</span>migrate
  <span style="color:#a61717;background-color:#e3d2d2">-</span> drupal<span style="color:#a61717;background-color:#e3d2d2">:</span>migrate_tools
  <span style="color:#a61717;background-color:#e3d2d2">-</span> drupal<span style="color:#a61717;background-color:#e3d2d2">:</span>migrate_plus
</code></pre></div><p>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 <code>/migration_csv_module/config/install/</code>.</p>
<p>La estructura tendrá este aspecto:</p>
<pre><code>/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
</code></pre><p>Necesitamos un CSV con los datos originales que vamos a migrar. Podemos generarlo fácilmente con herramientas web como <a href="https://www.mockaroo.com">Mockaroo, un generador de datos aleatorios bastante bueno</a>. He creado un CSV con los campos <code>id</code>, <code>title</code>, <code>body</code>, <code>tags</code> e <code>image</code>. Los recursos relacionados se mantienen ahora en <a href="https://gitlab.com/users/davidjguru/snippets">mis snippets de GitLab</a>.</p>
<p>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 <code>/csv</code>:</p>
<p>
<img src="/images/post/davidjguru_drupal_csv_migrate_module.png" alt="Estructura del módulo de migración CSV" loading="lazy" decoding="async">

</p>
<p>Y ahora, nuestro archivo <code>migrate_plus.migration.article_csv_import.yml</code>. En apartados posteriores explicaremos su construcción y sus secciones:</p>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">uuid<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">1</span>bcec3e7<span style="color:#099">-0</span>a49<span style="color:#099">-4473-87</span>a2<span style="color:#099">-6</span>dca09b91aba
langcode<span style="color:#a61717;background-color:#e3d2d2">:</span> en
status<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#000;font-weight:bold">true</span>
dependencies<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#a61717;background-color:#e3d2d2">{</span>  <span style="color:#a61717;background-color:#e3d2d2">}</span>
id<span style="color:#a61717;background-color:#e3d2d2">:</span> article_csv_import
label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Migrating articles&#39;</span>
source<span style="color:#a61717;background-color:#e3d2d2">:</span>
  plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> csv
  path<span style="color:#a61717;background-color:#e3d2d2">:</span> modules<span style="color:#a61717;background-color:#e3d2d2">/</span>custom<span style="color:#a61717;background-color:#e3d2d2">/</span>migration_csv_module<span style="color:#a61717;background-color:#e3d2d2">/</span>csv<span style="color:#a61717;background-color:#e3d2d2">/</span>migration_csv_articles.csv
  delimiter<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;,&#39;</span>
  enclosure<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;&#34;&#39;</span>
  header_offset<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#099">0</span>
  ids<span style="color:#a61717;background-color:#e3d2d2">:</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span> id
  fields<span style="color:#a61717;background-color:#e3d2d2">:</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      name<span style="color:#a61717;background-color:#e3d2d2">:</span> id
      label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Unique Id&#39;</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      name<span style="color:#a61717;background-color:#e3d2d2">:</span> title
      label<span style="color:#a61717;background-color:#e3d2d2">:</span> Title
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      name<span style="color:#a61717;background-color:#e3d2d2">:</span> body
      label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Post Body&#39;</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      name<span style="color:#a61717;background-color:#e3d2d2">:</span> tags
      label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Taxonomy Tag&#39;</span>
    <span style="color:#a61717;background-color:#e3d2d2">-</span>
      name<span style="color:#a61717;background-color:#e3d2d2">:</span> image
      label<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;Image Field&#39;</span>
process<span style="color:#a61717;background-color:#e3d2d2">:</span>
  title<span style="color:#a61717;background-color:#e3d2d2">:</span> title
  body<span style="color:#a61717;background-color:#e3d2d2">:</span> body
  tags<span style="color:#a61717;background-color:#e3d2d2">:</span> field_tags
  image<span style="color:#a61717;background-color:#e3d2d2">:</span> field_image
  type<span style="color:#a61717;background-color:#e3d2d2">:</span>
    plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> default_value
    default_value<span style="color:#a61717;background-color:#e3d2d2">:</span> article
destination<span style="color:#a61717;background-color:#e3d2d2">:</span>
  plugin<span style="color:#a61717;background-color:#e3d2d2">:</span> <span style="color:#d14">&#39;entity:node&#39;</span>
</code></pre></div><p>Ya tenemos todos los recursos necesarios para crear la nueva migración. Veamos cómo vamos a abordar el proceso.</p>
<h2 id="3-enfoques">3. Enfoques</h2>
<p>Describiremos los diferentes enfoques que aplicaremos a nuestros ejemplos para entenderlos mejor.</p>
<h3 id="primer-caso-migrar-datos-embebidos-1">Primer caso: migrar datos embebidos</h3>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3 id="segundo-caso-migrar-desde-archivos-csv-1">Segundo caso: migrar desde archivos CSV</h3>
<p>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.</p>
<p>Vamos a llevar la migración a otro lugar y ejecutarla desde allí. Sorpresa. Visita la ruta <code>/admin/config/development/configuration/single/import</code> de tu instalación Drupal. ¡Nos vemos allí!</p>
<h2 id="4-migraciones">4. Migraciones</h2>
<h3 id="primer-caso-migrar-datos-embebidos-2">Primer caso: migrar datos embebidos</h3>
<h4 id="consultar-las-migraciones-disponibles">Consultar las migraciones disponibles</h4>
<pre><code>drush migrate:status
drush ms

Output from console:
----------------- -------- ------- ---------- ------------- ---------------------
  Migration ID      Status   Total   Imported   Unprocessed   Last Imported
----------------- -------- ------- ---------- ------------- ---------------------
basic_migration_one   Idle     2       0          2
----------------- -------- ------- ---------- ------------- ---------------------
</code></pre><h4 id="ejecutar-las-migraciones">Ejecutar las migraciones</h4>
<pre><code>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
----------------- -------- ------- ---------- ------------- -------------------
</code></pre><p>Al visitar <code>/admin/content</code> verás los dos nodos nuevos:</p>
<p>
<img src="/images/post/davidjguru_drupal_migrations_basic_migration_embedded.png" alt="Migración básica de datos embebidos en Drupal" loading="lazy" decoding="async">

</p>
<h4 id="revertir-las-migraciones-deshacer-la-importación">Revertir las migraciones: deshacer la importación</h4>
<pre><code>drush migrate:rollback basic_migration_one
drush mr basic_migration_one

Output from console:

[notice] Rolled back 2 items - done with 'basic_migration_one'
</code></pre><p>
<img src="/images/post/davidjguru_drupal_migrations_basic_migration_command.png" alt="Comandos de la migración básica de Drupal" loading="lazy" decoding="async">

</p>
<h3 id="segundo-caso-migrar-desde-archivos-csv-2">Segundo caso: migrar desde archivos CSV</h3>
<p>En la ruta <code>/admin/config/development/configuration/single/import</code> importaremos el archivo de definición de nuestra migración personalizada.</p>
<h4 id="cargar-la-configuración-de-la-migración">Cargar la configuración de la migración</h4>
<p>Ve a Importar → Elemento individual, selecciona «Migration» como tipo de configuración y pega el contenido del archivo original de la migración:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_load_file_by_config.png" alt="Carga de la definición de migración mediante configuración" loading="lazy" decoding="async">

</p>
<p>Pulsa «Importar» y se creará el nuevo objeto en el sistema de configuración.</p>
<p><strong>¿Y ahora?</strong></p>
<h4 id="ejecutar-la-migración">Ejecutar la migración</h4>
<p>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 <code>drush migrate:status</code>:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_ask_for_migrations.png" alt="Estado de las migraciones con Drush" loading="lazy" decoding="async">

</p>
<p>Puedes ejecutar la migración con <code>drush migrate-import article_csv_import</code>. 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.</p>
<h4 id="drush-cex--drush-cim">Drush cex / Drush cim</h4>
<p>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 <code>drush cex</code>:</p>
<p>
<img src="/images/post/davidjguru_drupal_migration_drush_cex.png" alt="Exportación de configuración con Drush cex" loading="lazy" decoding="async">

</p>
<p>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 <code>migrate_plus.migration</code> o añadir un archivo para el grupo, que sirve para agrupar procesos de migración.</p>
<p>¿Recuerdas el nombre del archivo? Es el mismo que utilizábamos en <code>/config/install</code>: <code>migrate_plus.migration.article_csv_import.yml</code>. Hemos realizado el mismo proceso, pero desde otra dirección. ¿Te impresiona? ¿No? ¿Al menos te parece interesante?</p>
<p>Recuerda también que puedes utilizar <code>drush cim</code> 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.</p>
<p>Así hemos migrado unos 102 nodos nuevos utilizando dos enfoques y metodologías diferentes. No está mal.</p>
<h2 id="5-conceptos-clave">5. Conceptos clave</h2>
<h3 id="plugins-de-migración">Plugins de migración</h3>
<p>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.</p>
<p>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.</p>
<p>Vamos a revisar los plugins que intervienen en estas dos migraciones a partir de sus archivos de definición.</p>
<h4 id="migración-básica-con-datos-embebidos">Migración básica con datos embebidos</h4>
<pre><code>source:
  plugin: embedded_data
  data_rows:
         ...
process:
  title: creative_title
  body: engaging_content
destination:
  plugin: 'entity:node'
  default_bundle: page
</code></pre><p>Para extraer los datos utilizamos el plugin Embedded Data, una clase PHP disponible en <code>/web/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php</code>. En su bloque de documentación puedes ver algunas claves de configuración que puedes utilizar en la definición:</p>
<pre><code> *
 * Available configuration keys
 * - data_rows: The source data array.
 * - ids: The unique ID field of the data.
 *
</code></pre><p>Precisamente <code>data_rows</code> e <code>ids</code> son las claves que utilizamos: el array de datos de origen y su identificador único. <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21EmbeddedDataSource.php/class/EmbeddedDataSource/8.8.x">Puedes leer más sobre la clase EmbeddedDataSource en la documentación de la API de Drupal.org</a>.</p>
<p>Ahora fíjate en el bloque <code>process</code> y busca… <strong>¿dónde está el plugin de procesamiento?</strong> 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.</p>
<p>Ese plugin implícito es la clase Get, la solución básica para procesar campos. La encontrarás en <code>/web/core/modules/migrate/src/Plugin/migrate/process/Get.php</code>. <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21process%21Get.php/class/Get">Más información sobre Get.php en la API de Drupal.org</a>. En otras palabras, escribir esto:</p>
<pre><code>process:
  title: page_title
</code></pre><p>Equivale a escribir esto otro:</p>
<pre><code>process:
  title:
    plugin: get
    source: page_title
</code></pre><p>Así la vida resulta un poco más sencilla, ¿verdad? <strong>Recuerda: si no declaras un plugin de procesamiento para un campo, Drupal aplicará Get por defecto.</strong></p>
<p>Por último, en el destino utilizamos el plugin de entidad con el parámetro <code>node</code> para crear nodos del tipo <code>page</code>. Esto recurre al plugin de destino Entity.php, una clase abstracta ubicada en <code>web/core/modules/migrate/src/Plugin/migrate/destination/Entity.php</code>, y a su plugin derivado. Puedes leer más sobre <a href="https://www.drupal.org/docs/8/api/plugin-api/plugin-derivatives">los plugins derivados en Drupal</a>, <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21destination%21Entity.php/8.8.x">el plugin de destino Entity.php</a> y <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21Derivative%21MigrateEntity.php/class/MigrateEntity/8.8.x">la clase derivadora de migraciones</a>.</p>
<h4 id="migración-con-un-csv-como-fuente-de-datos">Migración con un CSV como fuente de datos</h4>
<p>Creo que en este caso revisar los plugins resulta más sencillo e intuitivo:</p>
<pre><code>source:
  plugin: csv
 ...
 process:
 ...
   type:
     plugin: default_value
     default_value: article
 destination:
   plugin: 'entity:node'
</code></pre><p>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.</p>
<h3 id="migraciones-como-código-o-como-configuración">Migraciones como código o como configuración</h3>
<p>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 <code>config/install</code>, la instalación puede crear un objeto de configuración.</p>
<p>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:</p>
<ul>
<li>Las migraciones «como código» están disponibles de serie. El módulo Migrate Plus permite tratar la definición como un objeto de configuración.</li>
<li>Según el enfoque, cambian la ubicación de los archivos y el flujo de trabajo.</li>
<li>Como código, necesitas acceso al sistema de archivos para modificar la definición y gestionarla como cualquier otro archivo de código. Es un enfoque orientado a desarrolladores.</li>
<li>Como configuración, puedes modificar la definición desde la interfaz de sincronización de Drupal, en <code>/admin/config/development/configuration</code>, y utilizar las herramientas de exportación e importación <code>drush cex</code> y <code>drush cim</code>. 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.</li>
<li>Como objeto de configuración, la migración crea un registro en el sistema de configuración que puede permanecer cuando se desinstale el módulo de migración. Para eliminarlo con el módulo, añade tu módulo personalizado como dependencia forzada en el YAML. Así se eliminará de la configuración activa al desinstalarlo:</li>
</ul>
<div class="highlight"><pre style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-toml" data-lang="toml">  dependencies<span style="color:#a61717;background-color:#e3d2d2">:</span>
    enforced<span style="color:#a61717;background-color:#e3d2d2">:</span>
      module<span style="color:#a61717;background-color:#e3d2d2">:</span>
        <span style="color:#a61717;background-color:#e3d2d2">-</span> my_own_migration_custom_module
</code></pre></div><ul>
<li>Otra diferencia del enfoque como configuración es el uso de un UUID: un identificador global del sistema de gestión de configuración de Drupal. Añade un UUID propio y único al principio del archivo. Su formato consta de 32 dígitos hexadecimales repartidos en cinco grupos con el patrón <code>8-4-4-4-12</code>. ¡Genera el tuyo! El original utiliza como ejemplo ilustrativo <code>uuid: cacafuti-1a23-2b45-3c67-4d567890a1b2</code>; contiene caracteres no hexadecimales, por lo que debes sustituirlo por un UUID válido.</li>
</ul>
<hr>
<h2 id="6-recursos">6. Recursos</h2>
<p>Descarga, prueba y experimenta con los recursos utilizados en el artículo. Los recursos relacionados se mantienen ahora en <a href="https://gitlab.com/users/davidjguru/snippets">mis snippets de GitLab</a>.</p>
<ol>
<li>Archivo de migración básica.</li>
<li>Archivo de migración CSV.</li>
<li>CSV de origen con datos aleatorios.</li>
<li>Código de los dos módulos de migración, básico y CSV, referenciado desde <a href="https://gitlab.com/davidjguru">mi perfil de GitLab</a>. Este será un repositorio central para los módulos de la serie, con los dos ejemplos:
<ul>
<li>Primera migración básica con datos embebidos.</li>
<li>Segunda migración básica con un CSV como fuente de datos.</li>
</ul>
</li>
<li>En paralelo a estos artículos publico snippets en GitLab sobre migraciones, con un formato más breve. Desde el primero puedes acceder al resto de la serie: <a href="https://gitlab.com/snippets/1916815">Drupal Migrations Tips (I): Creating a new basic migration structure</a>.</li>
</ol>
<h2 id="7-wq">7. :wq!</h2>
<h5 id="canción-recomendada">Canción recomendada</h5>


<div class="embed-responsive embed-responsive-16by9">
  <iframe
    class="embed-responsive-item"
    src="https://www.youtube-nocookie.com/embed/8hEYwk0bypY"
    title="YouTube video"
    loading="lazy"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowfullscreen></iframe>
</div>

]]></content:encoded></item></channel></rss>