Problem/Motivation

Complex migrations needs support code in the various events dispatched by migrate module. But an event subscriber react for every migration so a developer need to switch over the plugin_id to execute custom code only on desired migrations.

Proposed resolution

We can provide a generic event subscriber that delegate execution to custom classes (one for migration plugin id) that contains the code only for that migration.

Remaining tasks

Review code

User interface changes

None

API changes

None

Data model changes

None

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

lussoluca created an issue. See original summary.

lussoluca’s picture

lussoluca’s picture

Status: Active » Needs review
mikeryan’s picture

Project: Migrate Tools » Migrate Plus
Component: Code » API
Status: Needs review » Needs work
Issue tags: +Needs reroll

This provides new APIs extending core, there's no tool work here - migrate_plus is the appropriate module for this.

lussoluca’s picture

Status: Needs work » Needs review
FileSize
10.55 KB
mikeryan’s picture

Status: Needs review » Needs work
Issue tags: -Needs reroll

I like this! It beats having to check migration IDs in every event handler...

Besides the nits below, how about an example? I would suggest converting BeerNode::prepareRow() to use this model, with comments contrasting the approach with BeerUser::prepareRow() (extending the parent prepareRow()).

  1. +++ b/src/EventSubscriber/MigrateEventsSubscriber.php
    @@ -0,0 +1,126 @@
    +    if($migrationPluginManager->hasDefinition($plugin_id)) {
    

    Space after if.

  2. +++ b/src/EventSubscriber/MigrateEventsSubscriber.php
    @@ -0,0 +1,126 @@
    +   * @param \Drupal\migrate_plus\Event\MigratePrepareRowEvent $event
    

    Please fill in the docs for the onMigrate* methods.

  3. +++ b/src/MigrationEventsBase.php
    @@ -0,0 +1,78 @@
    +class MigrationEventsBase implements MigrationEventsInterface {
    

    Any reason not to make this abstract?

  4. +++ b/src/MigrationEventsPluginManager.php
    @@ -0,0 +1,29 @@
    +   *   keyed by the corresponding namespace to look for plugin implementations,
    

    s/,/./

lussoluca’s picture

Status: Needs work » Needs review
FileSize
17.15 KB

Nitpicks fixed and Node example updated (how do you think about the comments?)

mikeryan’s picture

Status: Needs review » Needs work
  1. +++ b/migrate_example/src/Plugin/migrate/source/BeerNode.php
    @@ -8,6 +8,13 @@ use Drupal\migrate\Row;
    + * @see \Drupal\migrate_plus\Plugin\migrate_tools\migration_events\BeerNodeMigrationEvents
    

    \Drupal\migrate_example\Plugin\migrate_plus\migration_events\BeerNodeMigrationEvents

  2. +++ b/migrate_example/src/Plugin/migrate_tools/BeerNodeMigrationEvents.php
    @@ -0,0 +1,46 @@
    +namespace Drupal\migrate_plus\Plugin\migrate_tools\migration_events;
    

    Drupal\migrate_example\Plugin\migrate_plus\migration_events

  3. +++ b/migrate_plus.services.yml
    @@ -11,3 +11,10 @@ services:
    +  plugin.manager.migrate_plus.migration:
    

    I think 'migration_events' rather than 'migration' as the last component here, to be clearer about its role (and not mistake it for a manager of migration plugins).

  4. +++ b/src/EventSubscriber/MigrateEventsSubscriber.php
    @@ -0,0 +1,168 @@
    +  /**
    +   * @param \Symfony\Component\EventDispatcher\Event $event
    +   * @param string $method
    +   */
    

    Please fill in the docs here.

  5. +++ b/src/MigrationEventsBase.php
    @@ -0,0 +1,118 @@
    +  /**
    +   * Event fired when preparing a source data row.
    +   *
    +   * @param \Drupal\migrate_plus\Event\MigratePrepareRowEvent $event
    +   *
    +   * @see \Drupal\migrate_plus\Event\MigrateEvents::PREPARE_ROW
    +   */
    

    The docs should be on the interface, and the implementations here should use {@inheritdoc}.

  6. +++ b/src/MigrationEventsInterface.php
    @@ -0,0 +1,30 @@
    +  public function onMigratePrepareRow(MigratePrepareRowEvent $event);
    +
    +  public function onMigratePreImport(MigrateImportEvent $event);
    +  public function onMigratePostImport(MigrateImportEvent $event);
    +  public function onMigratePreRowSave(MigratePreRowSaveEvent $event);
    +  public function onMigratePostRowSave(MigratePostRowSaveEvent $event);
    +  public function onMigratePreRollback(MigrateRollbackEvent $event);
    +  public function onMigratePostRollback(MigrateRollbackEvent $event);
    +  public function onMigratePreRowDelete(MigrateRowDeleteEvent $event);
    +  public function onMigratePostRowDelete(MigrateRowDeleteEvent $event);
    +  public function onMigrateIdmapMessage(MigrateIdMapMessageEvent $event);
    

    Please document the methods on the interfaces.

A broader issue - this is going to receive every single event that's dispatched during a migration and execute runEvent() for every one, how much overhead is that going to generate? Yes, runEvent() isn't that heavy, but it is going to get called a *whole lot* (3-4 times per imported row, twice per rolled-back row), it'll add up.

Just spitballing some thoughts here: What if MigrateEventsSubscriber has its own PRE_IMPORT handler (the only one in getSubscribedEvents()), which finds if there are any event plugins for the current migration and lazy-subscribes those events as needed (and, perhaps also builds a map of such plugins to optimize the actual dispatching). Problem - the base class implements every event handler, so we don't know when there's a "real" implementation...

Any other ideas on optimizing this?

Thanks!

heddn’s picture

So, this does help for custom migrations where we are writing event subscribers. This won't work in a generic sense (by contrib) though because we don't always know what a migration will be called.