Skip to content
Snippets Groups Projects
Verified Commit fed12777 authored by Dave Long's avatar Dave Long
Browse files

Issue #3245997 by mikelutz, martin_klima, quietone, danflanagan8, joachim,...

Issue #3245997 by mikelutz, martin_klima, quietone, danflanagan8, joachim, smustgrave, longwave: Allow process plugins to stop further processing on a pipeline
parent c10494df
No related branches found
No related tags found
26 merge requests!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8323Fix source code editing and in place front page site studio editing.,!6278Issue #3187770 by godotislate, smustgrave, catch, quietone: Views Rendered...,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #76412 passed with warnings
Pipeline: drupal

#76417

    ......@@ -425,6 +425,7 @@ protected function processPipeline(Row $row, string $destination, array $plugins
    }
    $break = FALSE;
    foreach ($value as $scalar_value) {
    $plugin->reset();
    try {
    $new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
    }
    ......@@ -437,6 +438,9 @@ protected function processPipeline(Row $row, string $destination, array $plugins
    $message = sprintf("%s: %s", $plugin->getPluginId(), $e->getMessage());
    throw new MigrateException($message);
    }
    if ($plugin->isPipelineStopped()) {
    $break = TRUE;
    }
    }
    $value = $new_value;
    if ($break) {
    ......@@ -444,6 +448,7 @@ protected function processPipeline(Row $row, string $destination, array $plugins
    }
    }
    else {
    $plugin->reset();
    try {
    $value = $plugin->transform($value, $this, $row, $destination);
    }
    ......@@ -456,7 +461,9 @@ protected function processPipeline(Row $row, string $destination, array $plugins
    $message = sprintf("%s: %s", $plugin->getPluginId(), $e->getMessage());
    throw new MigrateException($message);
    }
    if ($plugin->isPipelineStopped()) {
    break;
    }
    $multiple = $plugin->multiple();
    }
    }
    ......
    ......@@ -52,4 +52,17 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
    */
    public function multiple();
    /**
    * Determines if the pipeline should stop processing.
    *
    * @return bool
    * A boolean value indicating if the pipeline processing should stop.
    */
    public function isPipelineStopped(): bool;
    /**
    * Resets the internal data of a plugin.
    */
    public function reset(): void;
    }
    ......@@ -2,7 +2,6 @@
    namespace Drupal\migrate\Plugin\migrate\process;
    use Drupal\migrate\MigrateSkipProcessException;
    use Drupal\migrate\ProcessPluginBase;
    use Drupal\migrate\MigrateExecutableInterface;
    use Drupal\migrate\Row;
    ......@@ -131,7 +130,8 @@ public function row($value, MigrateExecutableInterface $migrate_executable, Row
    */
    public function process($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    if (!$value) {
    throw new MigrateSkipProcessException();
    $this->stopPipeline();
    return NULL;
    }
    return $value;
    }
    ......
    ......@@ -30,6 +30,13 @@
    */
    abstract class ProcessPluginBase extends PluginBase implements MigrateProcessInterface {
    /**
    * Determines if processing of the pipeline is stopped.
    *
    * @var bool
    */
    protected bool $stopPipeline = FALSE;
    /**
    * {@inheritdoc}
    */
    ......@@ -53,4 +60,25 @@ public function multiple() {
    return FALSE;
    }
    /**
    * {@inheritdoc}
    */
    public function isPipelineStopped(): bool {
    return $this->stopPipeline;
    }
    /**
    * {@inheritdoc}
    */
    public function reset(): void {
    $this->stopPipeline = FALSE;
    }
    /**
    * Stops pipeline processing after this plugin finishes.
    */
    protected function stopPipeline(): void {
    $this->stopPipeline = TRUE;
    }
    }
    ......@@ -150,6 +150,7 @@ protected function setPluginManagers() {
    $error_plugin_prophecy = $this->prophesize(MigrateProcessInterface::class);
    $error_plugin_prophecy->getPluginDefinition()->willReturn(['plugin_id' => 'test_error']);
    $error_plugin_prophecy->getPluginId()->willReturn('test_error');
    $error_plugin_prophecy->reset()->shouldBeCalled();
    $error_plugin_prophecy->transform(Argument::cetera())->willThrow(new MigrateException('Process exception.'));
    $this->processPluginManager->createInstance('get', Argument::cetera())
    ......
    ......@@ -336,6 +336,8 @@ public function testProcessRowPipelineException() {
    ->willReturn('transform_return_string');
    $plugin->multiple()->willReturn(TRUE);
    $plugin->getPluginId()->willReturn('plugin_id');
    $plugin->reset()->shouldBeCalled();
    $plugin->isPipelineStopped()->willReturn(FALSE);
    $plugin = $plugin->reveal();
    $plugins['destination_id'] = [$plugin, $plugin];
    $this->migration->method('getProcessPlugins')->willReturn($plugins);
    ......@@ -345,6 +347,63 @@ public function testProcessRowPipelineException() {
    $this->executable->processRow($row);
    }
    /**
    * Tests a plugin which stops the pipeline.
    */
    public function testStopPipeline() {
    $row = new Row();
    // Prophesize a plugin that stops the pipeline and returns 'first_plugin'.
    $stop_plugin = $this->prophesize(MigrateProcessInterface::class);
    $stop_plugin->getPluginDefinition()->willReturn(['handle_multiples' => FALSE]);
    $stop_plugin->transform(NULL, $this->executable, $row, 'destination_id')
    ->willReturn('first_plugin');
    $stop_plugin->multiple()->willReturn(FALSE);
    $stop_plugin->reset()->shouldBeCalled();
    $stop_plugin->isPipelineStopped()->willReturn(TRUE);
    // Prophesize a plugin that transforms 'first_plugin' to 'final_plugin'.
    $final_plugin = $this->prophesize(MigrateProcessInterface::class);
    $final_plugin->getPluginDefinition()->willReturn(['handle_multiples' => FALSE]);
    $final_plugin->transform('first_plugin', $this->executable, $row, 'destination_id')
    ->willReturn('final_plugin');
    $plugins['destination_id'] = [$stop_plugin->reveal(), $final_plugin->reveal()];
    $this->migration->method('getProcessPlugins')->willReturn($plugins);
    // Process the row and confirm that destination value is 'first_plugin'.
    $this->executable->processRow($row);
    $this->assertEquals('first_plugin', $row->getDestinationProperty('destination_id'));
    }
    /**
    * Tests a plugin which does not stop the pipeline.
    */
    public function testContinuePipeline() {
    $row = new Row();
    // Prophesize a plugin that does not stop the pipeline.
    $continue_plugin = $this->prophesize(MigrateProcessInterface::class);
    $continue_plugin->getPluginDefinition()->willReturn(['handle_multiples' => FALSE]);
    $continue_plugin->transform(NULL, $this->executable, $row, 'destination_id')
    ->willReturn('first_plugin');
    $continue_plugin->multiple()->willReturn(FALSE);
    $continue_plugin->reset()->shouldBeCalled();
    $continue_plugin->isPipelineStopped()->willReturn(FALSE);
    // Prophesize a plugin that transforms 'first_plugin' to 'final_plugin'.
    $final_plugin = $this->prophesize(MigrateProcessInterface::class);
    $final_plugin->getPluginDefinition()->willReturn(['handle_multiples' => FALSE]);
    $final_plugin->transform('first_plugin', $this->executable, $row, 'destination_id')
    ->willReturn('final_plugin');
    $final_plugin->multiple()->willReturn(FALSE);
    $final_plugin->reset()->shouldBeCalled();
    $final_plugin->isPipelineStopped()->willReturn(FALSE);
    $plugins['destination_id'] = [$continue_plugin->reveal(), $final_plugin->reveal()];
    $this->migration->method('getProcessPlugins')->willReturn($plugins);
    // Process the row and confirm that the destination value is 'final_plugin'.
    $this->executable->processRow($row);
    $this->assertEquals('final_plugin', $row->getDestinationProperty('destination_id'));
    }
    /**
    * Tests the processRow method.
    */
    ......@@ -361,6 +420,8 @@ public function testProcessRowEmptyDestination() {
    $plugin->getPluginDefinition()->willReturn([]);
    $plugin->transform(NULL, $this->executable, $row, $key)->willReturn($value);
    $plugin->multiple()->willReturn(TRUE);
    $plugin->reset()->shouldBeCalled();
    $plugin->isPipelineStopped()->willReturn(FALSE);
    $plugins[$key][0] = $plugin->reveal();
    }
    $this->migration->method('getProcessPlugins')->willReturn($plugins);
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\migrate\Unit\process;
    use Drupal\migrate\ProcessPluginBase as CoreProcessPluginBase;
    use Drupal\Tests\UnitTestCase;
    /**
    * Tests the base process plugin class.
    *
    * @group migrate
    *
    * @coversDefaultClass \Drupal\migrate\ProcessPluginBase
    */
    class ProcessPluginBaseTest extends UnitTestCase {
    /**
    * Tests stopping the pipeline.
    *
    * @covers ::isPipelineStopped
    * @covers ::stopPipeline
    * @covers ::reset
    */
    public function testStopPipeline() {
    $plugin = new ProcessPluginBase([], 'plugin_id', []);
    $this->assertFalse($plugin->isPipelineStopped());
    $stopPipeline = (new \ReflectionClass($plugin))->getMethod('stopPipeline');
    $stopPipeline->invoke($plugin);
    $this->assertTrue($plugin->isPipelineStopped());
    $plugin->reset();
    $this->assertFalse($plugin->isPipelineStopped());
    }
    }
    /**
    * Extends ProcessPluginBase as a non-abstract class.
    */
    class ProcessPluginBase extends CoreProcessPluginBase {
    }
    ......@@ -4,7 +4,6 @@
    namespace Drupal\Tests\migrate\Unit\process;
    use Drupal\migrate\MigrateSkipProcessException;
    use Drupal\migrate\MigrateSkipRowException;
    use Drupal\migrate\Plugin\migrate\process\SkipOnEmpty;
    ......@@ -21,9 +20,10 @@ class SkipOnEmptyTest extends MigrateProcessTestCase {
    */
    public function testProcessSkipsOnEmpty() {
    $configuration['method'] = 'process';
    $this->expectException(MigrateSkipProcessException::class);
    (new SkipOnEmpty($configuration, 'skip_on_empty', []))
    ->transform('', $this->migrateExecutable, $this->row, 'destination_property');
    $plugin = new SkipOnEmpty($configuration, 'skip_on_empty', []);
    $this->assertFalse($plugin->isPipelineStopped());
    $plugin->transform('', $this->migrateExecutable, $this->row, 'destination_property');
    $this->assertTrue($plugin->isPipelineStopped());
    }
    /**
    ......@@ -31,9 +31,11 @@ public function testProcessSkipsOnEmpty() {
    */
    public function testProcessBypassesOnNonEmpty() {
    $configuration['method'] = 'process';
    $value = (new SkipOnEmpty($configuration, 'skip_on_empty', []))
    $plugin = new SkipOnEmpty($configuration, 'skip_on_empty', []);
    $value = $plugin
    ->transform(' ', $this->migrateExecutable, $this->row, 'destination_property');
    $this->assertSame(' ', $value);
    $this->assertFalse($plugin->isPipelineStopped());
    }
    /**
    ......@@ -86,4 +88,26 @@ public function testRowSkipWithMessage() {
    $process->transform('', $this->migrateExecutable, $this->row, 'destination_property');
    }
    /**
    * Tests repeated execution of a process plugin can reset the pipeline stoppage correctly.
    */
    public function testMultipleTransforms() {
    $configuration['method'] = 'process';
    $plugin = new SkipOnEmpty($configuration, 'skip_on_empty', []);
    // Confirm transform will stop the pipeline.
    $value = $plugin
    ->transform('', $this->migrateExecutable, $this->row, 'destination_property');
    $this->assertNull($value);
    $this->assertTrue($plugin->isPipelineStopped());
    // Restart the pipeline and test again.
    $plugin->reset();
    $this->assertFalse($plugin->isPipelineStopped());
    $value = $plugin
    ->transform(' ', $this->migrateExecutable, $this->row, 'destination_property');
    $this->assertSame(' ', $value);
    $this->assertFalse($plugin->isPipelineStopped());
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment