9

How to skip (don't update) some fields from existing entity during import if this field already has a value? That for Drupal 8 with modules (migrate, migrate_plus and migrate_tools)

example of an yml I would like:

process:
  uid:
    -
      plugin: migration_lookup
      migration: users
      no_stub: true
      source: uid
  field_telephone: phone
  field_user_country: pays
  field_adresse/given_name: prenom
  field_adresse/family_name:
    -
      plugin: skip_on_notempty  \--> do not exist
      method: process
      source: \--> actuel value of 'field_adresse/family_name', How to get that ?
    -
      plugin: get
      source: nom
3
  • I need something similar, updating only certain fields. Nothing built in that I can find. this might be helpful, boylesoftware.com/blog/drupal-8-field-updates
    – troseman
    Nov 7, 2017 at 22:12
  • Have you found a solution for this? I need to update the value of a text field body/value, but leave the existing summary body/summary as is. The overwrite_properties option does not work here, as it works only for whole fields, not for sub values of a single field.
    – Hudri
    Feb 14, 2018 at 13:53
  • I don't think using "overwrite_properties" would give the right result - you don't want to overwrite the data if a value already exists right? I can't seem to find a process plugin that does this already so a custom one would be needed. They're easy enough to create. The existing skip_on_value would be a good start: git.drupalcode.org/project/migrate_plus/blob/HEAD/src/Plugin/…
    – joekers
    Aug 19, 2019 at 14:38

2 Answers 2

22

To import only a single field, or possibly multiple, use "overwrite_properties".

overwrite_properties: If the migration has specified a list of properties to be overwritten, clone the row with an empty set of destination values, and re-add only the specified properties.

destination:
  plugin: 'entity:node'
  overwrite_properties:
    - field_name

Source: Drupal\migrate\Plugin\migrate\destination

1
  • 3
    Wow, this is a hidden gem, your answer deserves to be marked "accepted". Mar 13, 2018 at 12:20
0

I'm a bit late to the party, but want to leave a comment for future readers of this question:

Obviously, there's no plugin in core or migrate_plus that allows skipping a migration, if the destination of an update is not empty. So we created a process plugin, that does exactly that:

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Skips migration updates, if the destination field is not empty.
 *
 * This process plugin is intended for content entity migrations. It prevents
 * field values from being overwritten, if the migration performs an update
 * (the destination entity exists) and the target field is NOT empty.
 *
 * Available configuration keys:
 * - method: (optional) What to do, if the destination field is not empty.
 *   Possible values:
 *   - row: Skips the entire row. (Default behavior.)
 *   - process: Prevents further processing of the input property.
 *
 * Example usage:
 *
 * Skip updating the entire destination entity, if its
 * 'field_ignore_if_not_empty' field is not empty; otherwise use the source
 * value from 'field_in_source'.
 *
 * @code
 * process:
 *   field_ignore_if_not_empty:
 *     plugin: skip_on_destination_not_empty
 *     source: field_in_source
 *     method: row
 * @endcode
 *
 * @MigrateProcessPlugin(
 *   id = "skip_on_destination_not_empty"
 * )
 */
class SkipOnDestinationNotEmpty extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  /**
   * The destination entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $entityStorage;

  /**
   * The migration.
   *
   * @var \Drupal\migrate\Plugin\MigrationInterface
   */
  protected $migration;

  /**
   * {@inheritdoc}
   *
   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
   *   The migration.
   * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
   *   The destination entity storage.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $entity_storage) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->entityStorage = $entity_storage;
    $this->migration = $migration;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    // Check, whether the migration is empty.
    if (empty($migration)) {
      throw new MigrateException('SkipOnDestinationNotEmpty plugin can only be used with a migration context.');
    }

    // We need to know the destination entity type, which we can get from the
    // migration's destination plugin ID, if it's a content entity
    // destination plugin ('entity:[entity_type_id]').
    $destination_plugin = $migration->getDestinationPlugin();
    $destination_plugin_id = $destination_plugin->getPluginId();
    $entity_type_id = NULL;
    if (strpos($destination_plugin_id, static::DERIVATIVE_SEPARATOR)) {
      [, $entity_type_id] = explode(static::DERIVATIVE_SEPARATOR, $destination_plugin_id, 2);
    }

    // If we didn't find an entity type, we should stop here.
    if (empty($entity_type_id)) {
      throw new MigrateException('SkipOnDestinationNotEmpty plugin can only be used with content entity migrations.');
    }

    // If the entity storage doesn't exist, we stop here as well.
    $storage = $container->get('entity_type.manager')->getStorage($entity_type_id);
    if (empty($storage)) {
      throw new MigrateException("SkipOnDestinationNotEmpty plugin can't initialize the entity storage for {$entity_type_id}.");
    }

    // Inject the entity storage into the constructor.
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $storage
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    // Determine the destination entity ID. We use the migration's ID map for
    // update migrations, or an entity ID value provided in the migration
    // process instructions.
    $id_map = $this->migration->getIdMap();
    $destination_ids = $id_map->lookupDestinationIds($row->getSourceIdValues());
    $destination_id_values = $destination_ids ? reset($destination_ids) : [];
    $entity_id = reset($destination_id_values) ?: $row->getDestinationProperty($this->entityStorage->getEntityType()->getKey('id'));

    // Whether a target entity ID exists and the entity can be loaded.
    if (!empty($entity_id) && ($entity = $this->entityStorage->load($entity_id))) {
      // Whether the entity has the destination field and it's not empty.
      if ($entity->hasField($destination_property) && !$entity->get($destination_property)->isEmpty()) {
        // Whether process skipping has been configured.
        if (!empty($this->configuration['mode']) && $this->configuration['mode'] == 'process') {
          throw new MigrateSkipProcessException("Skipped processing $destination_property on {$this->entityStorage->getEntityType()->id()} {$entity->id()} due to destination field is not empty.");
        }
        // Skip the row.
        else {
          throw new MigrateSkipRowException("Skipped updating {$this->entityStorage->getEntityType()->id()} {$entity->id()} due to $destination_property field is not empty.");
        }
      }
    }

    // Return the not altered value.
    return $value;
  }

}

Now, this plugin skips, if the entire destination field is not empty, and it can be used on the first level of the field process only.

It would yield strange results, if used on a field property, as it uses the transform process plugin method to determine the field name via the $destination_property parameter, which reflects the field property name on the second level (e.g. target_id, value, summary, ...).

BUT you can easily adapt it to do some different checks on the destination field. E.g., checking certain field properties. All you have to do is altering the line

if ($entity->hasField($destination_property) && !$entity->get($destination_property)->isEmpty()) {

to your needs.

Please also note, that this plugin does NOT support multilanguage entities and destination languages. That wasn't required for our task at hand, but you should be aware of it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.