diff --git a/migrate_tools.services.yml b/migrate_tools.services.yml index 6b60629..b86becb 100644 --- a/migrate_tools.services.yml +++ b/migrate_tools.services.yml @@ -10,4 +10,10 @@ services: migrate_tools.migration_drush_command_progress: class: Drupal\migrate_tools\EventSubscriber\MigrationDrushCommandProgress tags: - - { name: event_subscriber, priority: 0 } + - { name: event_subscriber } + migrate_tools.migration_sync: + class: Drupal\migrate_tools\EventSubscriber\MigrationImportSync + tags: + - { name: event_subscriber } + arguments: + - '@event_dispatcher' diff --git a/src/Commands/MigrateToolsCommands.php b/src/Commands/MigrateToolsCommands.php index 7a9a1d7..70a5b89 100644 --- a/src/Commands/MigrateToolsCommands.php +++ b/src/Commands/MigrateToolsCommands.php @@ -256,6 +256,8 @@ class MigrateToolsCommands extends DrushCommands { * satisfied * @option execute-dependencies Execute all dependent migrations first. * @option skip-progress-bar Skip displaying a progress bar. + * @option sync Sync source and destination. Delete destination records that + * do not exist in the source. * * @default $options [] * @@ -295,6 +297,7 @@ class MigrateToolsCommands extends DrushCommands { 'force' => FALSE, 'execute-dependencies' => FALSE, 'skip-progress-bar' => FALSE, + 'sync' => FALSE, ]) { $group_names = $options['group']; $tag_names = $options['tag']; @@ -748,6 +751,9 @@ class MigrateToolsCommands extends DrushCommands { $executed_migrations += $required_migrations; } } + if ($options['sync']) { + $migration->set('syncSource', TRUE); + } if ($options['skip-progress-bar']) { $migration->set('skipProgressBar', TRUE); } diff --git a/src/EventSubscriber/MigrationImportSync.php b/src/EventSubscriber/MigrationImportSync.php new file mode 100644 index 0000000..6586ec9 --- /dev/null +++ b/src/EventSubscriber/MigrationImportSync.php @@ -0,0 +1,75 @@ +dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = []; + $events[MigrateEvents::POST_IMPORT][] = ['sync']; + return $events; + } + + /** + * Event callback to sync source and destination. + */ + public function sync(MigrateImportEvent $event) { + $migration = $event->getMigration(); + if (!empty($migration->syncSource)) { + $id_map = $migration->getIdMap(); + $id_map->prepareUpdate(); + $source = $migration->getSourcePlugin(); + $source->rewind(); + $source_id_values = []; + while ($source->valid()) { + $source_id_values[] = $source->current()->getSourceIdValues(); + $source->next(); + } + $id_map->rewind(); + $destination = $migration->getDestinationPlugin(); + while ($id_map->valid()) { + $map_source_id = $id_map->currentSource(); + if (!in_array($map_source_id, $source_id_values, TRUE)) { + $destination_ids = $id_map->currentDestination(); + $this->dispatcher->dispatch(MigrateEvents::PRE_ROW_DELETE, new MigrateRowDeleteEvent($event->getMigration(), $destination_ids)); + $this->dispatcher->dispatch(MigratePlusEvents::MISSING_SOURCE_ITEM, new MigrateRowDeleteEvent($event->getMigration(), $destination_ids)); + $destination->rollback($destination_ids); + $this->dispatcher->dispatch(MigrateEvents::POST_ROW_DELETE, new MigrateRowDeleteEvent($event->getMigration(), $destination_ids)); + $id_map->delete($source_id_values); + } + $id_map->next(); + } + } + } + +} diff --git a/tests/src/Functional/DrushCommandsTest.php b/tests/src/Functional/DrushCommandsTest.php index 93e92ca..ec90fc0 100644 --- a/tests/src/Functional/DrushCommandsTest.php +++ b/tests/src/Functional/DrushCommandsTest.php @@ -60,9 +60,9 @@ class DrushCommandsTest extends BrowserTestBase { public function testDrush() { $this->drush('ms', [], [], NULL, NULL, 1); $this->assertContains('The "does_not_exist" plugin does not exist.', $this->getErrorOutput()); - $this->drush('cdel', ['migrate_plus.migration.invalid_plugin']); + $this->container->get('config.factory')->getEditable('migrate_plus.migration.invalid_plugin')->delete(); // Flush cache so the recently removed invalid migration is cleared. - $this->drush('cr'); + drupal_flush_all_caches(); $this->drush('ms', [], ['format' => 'json']); $expected = [ [ @@ -131,4 +131,35 @@ class DrushCommandsTest extends BrowserTestBase { $this->assertErrorOutputEquals('[notice] Rolled back 3 items - done with \'fruit_terms\''); } + /** + * Tests synced import. + */ + public function testSyncImport() { + $this->drush('mim', ['fruit_terms']); + $expected = [ + '1/3 [=========>------------------] 33%', + ' 2/3 [==================>---------] 66%', + ' 3/3 [============================] 100% [notice] Processed 3 items (3 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'', + ]; + $this->assertEquals($expected, $this->getErrorOutputAsList()); + $term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2); + $this->assertEquals('Banana', $term->label()); + $this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->count()->execute()); + $source = $this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->get('source'); + unset($source['data_rows'][1]); + $this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save(); + // Flush cache so the recently changed migration can be refreshed. + drupal_flush_all_caches(); + $this->drush('mim', ['fruit_terms'], ['sync' => NULL]); + $expected = [ + '1/2 [==============>-------------] 50% [notice] Processed 0 items (0 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'', + '', + ' 2/2 [============================] 100%', + ' 3/3 [============================] 100%', + ]; + $this->assertEquals($expected, $this->getErrorOutputAsList()); + $this->assertEquals(2, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->count()->execute()); + $this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2)); + } + } diff --git a/tests/src/Kernel/DrushTest.php b/tests/src/Kernel/DrushTest.php index 46e6d5e..4ff46e4 100644 --- a/tests/src/Kernel/DrushTest.php +++ b/tests/src/Kernel/DrushTest.php @@ -43,6 +43,7 @@ class DrushTest extends MigrateTestBase { 'force' => NULL, 'execute-dependencies' => NULL, 'skip-progress-bar' => FALSE, + 'sync' => FALSE, ]; /**