Evento MigratePostRowSave en Migraciones de redirecciones en Drupal 8

El sistema de migración en Drupal 8 es muy poderoso. En muy pocas líneas de código, puedes conectar varias fuentes de datos, procesar e importar datos a Drupal.

Uno de esos datos importantes es la transferencia de URL y redirecciones para contenido heredado. La buena noticia es que las redirecciones son entidades en Drupal 8, por lo que escribir una migración específicamente para ellas es bastante sencillo y se puede ejecutar después de haber importado el resto del contenido. Se parecen a cualquier otra definición de migración, con su destino como entity:redirect.

Sin embargo, a veces no podemos adquirir datos de la fuente de la forma en que los necesitamos. En este escenario, las redirecciones son parte del registro de origen del elemento como una matriz. La migración yaml solo procesa 1 origen a 1 destino para una entidad. Incluso si pudieras usar el complemento sub_process para agrupar los registros de redireccionamiento y procesarlos más tarde, no podrías evitar que tengan el mismo ID de origen y probablemente no funcionen como esperabas. Además, si está haciendo migraciones continuas en lugar de una migración única, crearías un acoplamiento de +1 migraciones entre sí cada vez que necesite ejecutarlas. Entonces, ¿cómo podemos resolver esto?

Eventos al rescate

El sistema Migrate proporciona una serie de eventos para escuchar cuando se ejecutan migraciones. La clave para ayudarnos a resolver nuestro problema aquí será MigratePostRowSaveEvent . Este evento se dispara después de que un elemento se haya guardado en una migración, lo que nos da la oportunidad de incorporar también nuestros datos de redireccionamiento con el elemento.

Primero, podemos hacer una modificación a nuestras migraciones que tienen redireccionamientos provistos, por lo que podemos verificar en nuestro caso si se realiza o no algún procesamiento:

id: working_paper
label: Working Papers
migration_tags:
  - working papers
source:
  has_redirects: true
  plugin: url
  urls:
    - 'private://migration/working_paper.json'

...

Con esta configuración en la configuración de origen, podemos leerla en nuestro evento personalizado.

En segundo lugar, proporcionamos el suscriptor del evento y algo de código para crear entidades de redireccionamiento: 

<?php

declare(strict_types = 1);

namespace Drupal\nber_migration\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\migrate\Event\EventBase;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\redirect\Entity\Redirect;

/**
 * Class EntityRedirectMigrateSubscriber.
 *
 * @package Drupal\nber_migration\EventSubscriber
 */
class EntityRedirectMigrateSubscriber implements EventSubscriberInterface {

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * EntityRedirectMigrateSubscriber constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Helper method to check if the current migration has redirects in its source.
   *
   * @param \Drupal\migrate\Event\EventBase $event
   *   The migrate event.
   *
   * @return bool
   *   True if the migration is configured with has_redirects.
   */
  protected function hasRedirects(EventBase $event) : bool {
    $migration = $event->getMigration();
    $source_configuration = $migration->getSourceConfiguration();
    return !empty($source_configuration['has_redirects']) && $source_configuration['has_redirects'] == TRUE;
  }

  /**
   * Maps the existing redirects to the new node id.
   *
   * @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
   *   The migrate post row save event.
   */
  public function onPostRowSave(MigratePostRowSaveEvent $event) : void {
    if ($this->hasRedirects($event)) {
      $row = $event->getRow();
      $source = $row->getSource();
      $id = $event->getDestinationIdValues();
      $id = reset($id);
      $redirects = $source["redirects"];

      if (count($redirects)) {
        foreach ($redirects as $redirect) {
          // check for duplicate first by path
          $redirect = ltrim($redirect, '/');
          $records = $this->entityTypeManager->getStorage('redirect')->loadByProperties(['redirect_source__path' => $redirect]);

          if (empty($records)) {
            Redirect::create([
              'redirect_source' => [
                'path' => $redirect,
                'query' => [],
              ],
              'redirect_redirect' => 'internal:/node/' . $id,
              'language' => 'en',
              'status_code' => '301',
            ])->save();
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {
    $events = [];
    $events[MigrateEvents::POST_ROW_SAVE] = ['onPostRowSave'];
    return $events;
  }
}

Ahora, cuando se migra una fila, las redirecciones se procesan y se crean si aún no existen.