0

I was learning about Drupal migration recently and found out that we can use Migrate Plus module to import migrations as configurations. Hence I started wondering if a migration is imported as a configuration, is it possible to override/update the migration's process plugin field mapping through a custom form using the "config.factory" service. I tried implementing this and found out that the configuration update through the custom form is not taking effect in the migration end, until and unless I flush the cache in the custom form's submit handler. I feel like this is not the right way. Is there any other way to implement this without clearing the cache every time the form submits?

The submit handler for my config form looks like this -> I am trying to change the mapping of the entity:node:body field with a different source plugin field whose id is stored in the selectfield field in the form.

public function submitForm(array &$form, FormStateInterface $form_state)
  {
    $config_factory = \Drupal::configFactory();
    $config = $config_factory->getEditable('migrate_plus.migration.data');
    $process = $config->get('process');
    $process['body'] = $form_state->getValue("selectfield");
    $config->set('process', $process);
    $config->save();
    $this->config("config.migrateform")->set("selectfield", $form_state->getValue("selectfield"))->save();
    drupal_flush_all_caches();
  }

1 Answer 1

0

Your solution changes the migration configuration via form which would then require config export if you have staging, prod etc. environments to import the new config.

Which in the end makes you think what is the point of having a form in the first place when you can edit migration yaml file directly to achieve the same result.

However to answer the question two solutions come to mind:

Both solutions should have the form submit save only the value of the field.

public function submitForm(array &$form, FormStateInterface $form_state) {
  $this->config('config.migrateform')
    ->set('selectfield', $form_state->getValue('selectfield'))
    ->save();
}

1. Using an event subscriber to the migrate.pre_import event

Migration import fires the event at the start of the migration.

$this->getEventDispatcher()->dispatch(new MigrateImportEvent($this->migration, $this->message), MigrateEvents::PRE_IMPORT);

You can subscribe to this event and get the migration object from the event.

Migration object allows you to get and/or set the process plugin definitions.

/**
 * Gets the normalized process plugin configuration.
 *
 * The process configuration is always normalized. All shorthand processing
 * will be expanded into their full representations.
 *
 * @see https://www.drupal.org/node/2129651#get-shorthand
 *
 * @return array
 *   The normalized configuration describing the process plugins.
 */
public function getProcess();

/**
 * Allows you to override the entire process configuration.
 *
 * @param array $process
 *   The entire process pipeline configuration describing the process plugins.
 *
 * @return $this
 */
public function setProcess(array $process);

So in the event subscriber you can load the config.migrateform and the value selectfield then replace the process configuration.

public function onPreImport(MigrateImportEvent $event): void {
  $migration = $event->getMigration();
  // Don't do anything if migration is not the one you want to alter
  if ($migration->getPluginId() !== 'my_migration_id') {
    return;
  }

  $fieldProcess = \Drupal::configFactory()->get('config.migrateform')->get('selectfield');
  $process = $migration->getProcess();
  $process['body'] = $fieldProcess;
  $migration->setProcess($process);
}

This is just a subscriber method snippet, complete solution should use dependency injection.

This will alter the migration on the fly but will not update the yaml configuration which might confuse other developers looking at the migration yaml file but the migration would act differently, so it might be a good idea to add a comment to the yaml file that the event subscriber alters the behaviour. To avoid this confusion the second solution can be used.

Another issue here is that the subscriber applies to the migration id(s) you specify in the if block which would need updating if you want to use it in multiple migrations.

2. Creating a custom plugin

This solution requires creating a custom process plugin that can dynamically load another process plugin from form configuration and delegate the processing to the loaded plugin.

Create a custom process plugin:

/**
 * @MigrateProcessPlugin(
 *   id = "dynamic_process_plugin"
 * )
 */
class DynamicProcessPlugin extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  protected $configFactory;

  protected $pluginManager;

  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ConfigFactoryInterface $factory, MigratePluginManagerInterface $pluginManager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->configFactory = $factory;
    $this->pluginManager = $pluginManager;
  }

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('config.factory'),
      $container->get('plugin.manager.migrate.process')
    );
  }

  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $fieldProcess = $this->configFactory->get('config.migrateform')->get('selectfield');
    $processPlugin = $this->pluginManager->getInstance($fieldProcess);

    return $processPlugin->transform($value, $migrate_executable, $row, $destination_property);
  }

}

And use this custom plugin in the migration yaml for whatever field you want - in your case for body:

process:
  body:
    plugin: dynamic_process_plugin
    source: body

This solution is more flexible as you can add use the plugin in any migration on any field you want and there is no confusion for other developers.

Note that I have not tested this code and it is not complete - missing namespaces, incomplete coding standards etc.

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.