Skip to main content
August 29, 2017

How to skip row if parent entity does not exist during a Drupal 8 migration

by Richard Papp

Drupal 8 lets you perform incremental migrations and update records that have changed since their last migration. If you meanwhile delete such records in Drupal 8 a migration update may break.

We ran into this issue on updating migrated forum nodes that had since received further comments on the Drupal 6 source website. Their parent node didn't exist anymore in Drupal 8:

TypeError: Argument 1 passed to Drupal\forum\ForumIndexStorage::updateIndex() 
must implement interface Drupal\node\NodeInterface, null given, 
called in /core/modules/forum/forum.module
on line 272 in Drupal\forum\ForumIndexStorage->updateIndex()
(line 92 of /core/modules/forum/src/ForumIndexStorage.php)
#0 /core/modules/forum/forum.module(272):
Drupal\forum\ForumIndexStorage->updateIndex(NULL)

To prevent such errors we wrote the process plugin skip_row_if_not_exist. You can configure the entity type and its property to check for and a migration message:

<?php

namespace Drupal\migrate_custom\Plugin\migrate\process;

use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateSkipRowException;

/**
 * Skips processing the current row when a destination value does not exist.
 *
 * The skip_row_if_not_exist process plugin checks whether a value exists. If the
 * value exists, it is returned. Otherwise, a MigrateSkipRowException
 * is thrown.
 *
 * Available configuration keys:
 * - entity: The destination entity to check for.
 * - property: The destination entity property to check for.
 * - message: (optional) A message to be logged in the {migrate_message_*} table
 *   for this row. If not set, nothing is logged in the message table.
 *
 * Example:
 *  Do not import comments for migrated nodes that do not exist any more at the
 *  destination.
 *
 * @code
 *  process:
 *    entity_id:
 *    -
 *      plugin: migration_lookup
 *      migration:
 *        - d6_node
 *      source: nid
 *    -
 *      # Check if a node exists in the destination database.
 *      plugin: skip_row_if_not_exist
 *      entity: node
 *      property: nid
 *      message: 'Commented entity not found.'
 * @endcode
 *
 * This will return the node id if it exists. Otherwise, the row will be
 * skipped and the message "Commented entity not found." will be logged in the
 * message table.
 *
 * @see \Drupal\migrate\Plugin\MigrateProcessInterface
 *
 * @MigrateProcessPlugin(
 *   id = "skip_row_if_not_exist",
 *   handle_multiples = TRUE
 * )
 */
class SkipRowIfNotExist extends ProcessPluginBase {

  protected $entity = 'node';
  protected $property = 'nid';
  
  function __construct($configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    if (!empty($configuration['entity'])) {
      $this->entity = $configuration['entity'];
    }

    if (!empty($configuration['property'])) {
      $this->property = $configuration['property'];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $count = \Drupal::entityQuery($this->entity)
      ->condition($this->property, $value)
      ->accessCheck(FALSE)
      ->count()
      ->execute();

    if (!$count) {
      $message = isset($this->configuration['message']) ? $this->configuration['message'] : '';
      throw new MigrateSkipRowException($message);
    }

    return $value;
  }

}

Example

To check if the parent node of a comment exists, you would do the following:

  1. Copy /core/modules/comment/migration_templates/d6_comment.yml to a custom migration module.
  2. Replace entity_id: nid with two process plugins
    1. Lookup the destination nid of the parent node in the migration mapping table
    2. Skip the whole row if the node doesn't exist

Before (excerpt)

process:
  # If you are using this file to build a custom migration consider removing
  # the cid field to allow incremental migrations.
  cid: cid
  pid:
    plugin: migration_lookup
    migration: d6_comment
    source: pid
  entity_id: nid

After (excerpt)

process:
  # If you are using this file to build a custom migration consider removing
  # the cid field to allow incremental migrations.
  cid: cid
  pid:
    plugin: migration_lookup
    migration: d6_comment
    source: pid
  entity_id:
    -
      plugin: migration_lookup
      migration:
        - d6_node
      source: nid
    -
      plugin: skip_row_if_not_exist
      entity: node
      property: nid
      message: 'Commented entity not found.'

 

Tools

Comments

Angus - apratt… (not verified)

Exactly the error I was seeing and much better than deleting the rows in the comment table which how I was initially solving the problem.

I did run into a little problem with figuring out where to put the process plugin. I eventually figured it out with the help of this article from Advomatic

The file system in Drupal 8 is organized very differently than in Drupal 7. Within your custom module, plugins will always go in [yourmoduledir]/src/Plugin. In this case, our migrate process plugin goes in [yourmodulename]/src/Plugin/migrate/process/[process-name].php.

Thanks again.

Thu, 31.08.2017 - 18:58 Permalink

Add new comment

Klartext

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
CAPTCHA