Bean Migrate is the next migration path module founded by Acquia. Drupal 7 Bean does not have a Drupal 8+ release, because the majority of its features are available in Drupal 8+, provided by the Custom Block module.

Drupal 7 Bean is basically the ancestor of the Custom Block module. It allows users to create blocks of content (these are bean entities). Bean entities can have fields, view modes; they are revisionable; and can be placed into regions like blocks provided by other modules.

Don’t change the value a source identifier

You shouldn’t ever change the value of a source property which is a source identifier. I did that. In every d7_field* migration, if the value of the entity_type was bean, I changed it to block_content. Then we saw that the fields which were already migrated (and which weren’t touched on the source) are considered as not-yet migrated source items. And when we tried to re-import them, the operation ended up in an EntityStorageException complaining about duplicate IDs.

Why did this happen? Well, Migrate API assumes that these identifier properties aren’t changed during the migration. And this is not necessarily a bug. (I mean, I can live with that.)

What actually happens is that MigrateExecutable gets the source IDs of the row before any processing happens. It also uses these early values when it tries to check whether the ID map plugin tracks any destination IDs about the current source. But when the successfully imported row’s data is saved by the ID map plugin, it does not access these early-evaluated source IDs: it only gets the fully processed Row. So it will fetch the source IDs from this object.

And if you have changed these IDs, then it will save the changed source IDs.

Never-ever modify the process pipeline of a migration at the prepare row phase

If you do so, you will get bug reports like this: #3205133 Migrated field configurations and view modes get wrong entity type of block_content

And it is so obvious… I don’t even know why I didn’t have any doubts about that! If you change the process pipelines on certain conditions, you will have to restore the original state right after the conditions aren’t met.

It’s just more clear, robust, less confusing and maintainable if you solve these issues by adding more process plugin configuration to the process pipeline you want to modify in a hook_migration_plugins_alter() implementation.

Core sometimes does pointless things

The issues I had to report to core:

  1. #3200936 DX: Block (config) destination shouldn’t recalculate block config ID for block translations

  2. #3200949 Non-default entity revisions are migrated as default revision because EntityContentComplete does not allow creating forward (and non-default) revisions

Some features are still missing from core Migrate API

Initially I tried to migrate the block placements (aka Block configuration entities) by modifying Drupal core’s d7_block migration. But unfortunately, it isn’t customizable at all, mostly because of the BlockPluginId process plugin (and on the other hand because of the process pipeline of the plugin destination property in the d7_block migration).

The process plugin was designed to be used in this process pipeline, and the process pipeline was built on the top of this process plugin. 😳

BlockPluginId shouldn’t reset the source value if it is an array and it does not fit in any case statements; it should return the incoming value without any changes.

And instead of the last skip_on_empty process, there should be another process plugin which skips the migration of the actual row if it cannot find a block plugin ID based on the value it gets:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Checks whether the source value is a valid block plugin ID.
 * 
 * @return string
 */
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
  $value = implode(PluginBase::DERIVATIVE_SEPARATOR, (array) $value);
  if (!$this->blockPluginManager->hasDefinition($value)) {
    throw new MigrateSkipRowException();
  }
  return $value;
}