Problem/Motivation

Drupal provides a concat process plugin, that can transform multiple source values into a single value field, using the implode() function. It would be extremely useful to have another process plugin with the reverse functionality of exploding a single value source into multi-value fields.

Proposed resolution

Add a process plugin that can be used to explode a single value into a multiple value field.

Remaining tasks

None

User interface changes

None

API changes

None

Data model changes

None

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tannerjfco created an issue. See original summary.

tannerjfco’s picture

Is this a documentation issue (i.e. needs to be written) or is this an issue of additional functionality required in MigrateSourceCSV? Looking for some insight so I know how to proceed.

maijs’s picture

I don't regard this as a migrate_plus issue. migrate core module doesn't provide Explode process plugin to explode multiple values from a single line in source, regardless whether that's XML, CSV or DB. Fortunately, it's very simple to create such a process plugin. (See an excellent example in this article - Migrating to Drupal 8, see Users section.)

In essence:

1. Create a file MY_MODULE/src/Plugin/migrate/process/Explode.php
2. Put in the following code:


/**
 * @file
 * Contains \Drupal\MY_MODULE\Plugin\migrate\callback\Explode.
 */

namespace Drupal\MY_MODULE\Plugin\migrate\process;

use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;

/**
 * @MigrateProcessPlugin(
 *   id = "explode",
 * )
 */
class Explode extends ProcessPluginBase {
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $result = explode($this->configuration['delimiter'], $value);
    return $result;
  }
}

3. In migration configuration file use the following:

my_dest_field:
  -
    plugin: explode
    delimiter: ','
    source: my_source_field
heddn’s picture

Project: Migrate Plus » Migrate Source CSV
Version: 8.x-1.x-dev »
Component: Source plugins » Code

Re-assigning to new project name-space.

heddn’s picture

Project: Migrate Source CSV » Drupal core
Version: » 8.0.x-dev
Component: Code » migration system
Category: Support request » Feature request

Splitting by a comma or other delimiter didn't make it into core yet. I'm moving this over to the core issue queue for the time being to see if we can still fit it into things at this late stage. If not, this is the type of thing that will get added to migrate_plus. To be clear, this isn't specific to CSV migrations, it is general.

In the mean-time, this can be done in a prepare row callback.

mikeryan’s picture

Title: MigrateSourceCSV - handling multiple values » Add an explode/separator process plugin

Retitling to reflect the current direction.

quietone’s picture

@mikeryan, should I make a patch for this with a test?

mikeryan’s picture

@quietone: well, someone should at some point, but with a week till release I wouldn't prioritize new features for 8.0.0 - my inclination is to put this into migrate_plus for now and aim to submit to core for 8.1.0. In general, I think at this point the priority should be trying to get the more significant "needs review" issues to RTBC, and triaging open bug reports to see if there's anything we really want to try to get fixed in the next week.

quietone’s picture

Status: Active » Needs review
FileSize
4.04 KB

Ended up writing this while working on the D7 color and theme variable migrations.

Status: Needs review » Needs work

The last submitted patch, 9: 2575101-9.patch, failed testing.

quietone’s picture

Status: Needs work » Needs review
FileSize
4.01 KB
655 bytes

Fixed the namespace.

benjy’s picture

+++ b/core/modules/migrate/src/Plugin/migrate/process/Explode.php
@@ -0,0 +1,41 @@
+    if (is_string($value[0])) {

+++ b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php
@@ -0,0 +1,81 @@
+    $source = ['color_bartik_stylesheets'];

Why do we check value[0], that should be just $value? And then $source wouldn't be an array.

quietone’s picture

hussainweb’s picture

Making a small change. I don't see why sprintf is used there (maybe copy/paste error). And I think this needs to go to 8.1.x.

benjy’s picture

Version: 8.1.x-dev » 8.0.x-dev

Migrate is marked as experimental, I don't see why this has to wait until 8.1

heddn’s picture

Status: Needs review » Reviewed & tested by the community

I've looked over the code. It looks solid.

+++ b/core/modules/migrate/src/Plugin/migrate/process/Explode.php
@@ -0,0 +1,41 @@
+      throw new MigrateException(sprintf('%s is not a string', var_export($value, TRUE)));

Are we sure we want to use var_export? Couldn't that return nasty things?

heddn’s picture

Status: Reviewed & tested by the community » Needs review
hussainweb’s picture

@heddn: That is a general pattern in all the migrate plugins.

heddn’s picture

Status: Needs review » Needs work

It isn't that common in Migrate. And I think we'd be better off not introducing more occurrences.

benjy’s picture

I don't see a problem with var_export, what do you suggest instead? Happy with print_r as well for me.

heddn’s picture

Either is fine, but SafeMarkup::checkPlain() the values before inserting them into the exception.

benjy’s picture

Why would we do that? checkPlain is for escaping a string that is going to be rendered in a HTML document, we're throwing an exception. If something else is catching that exception and printing it to the page, that's their problem.

heddn’s picture

Doesn't watchdog grab those? So it would be printing that all to the screen for us. Does it do the checkPlain()?

benjy’s picture

If watchdog is printing it to the screen, then watchdog will be escaping it.

heddn’s picture

Status: Needs work » Needs review

In that case, I'm probably being paranoid.

chr.fritsch’s picture

We should add multiple true, because that other process plugins in the pipeline should be able to iterate over the exploded values

benjy’s picture

+++ b/core/modules/migrate/src/Plugin/migrate/process/Explode.php
@@ -0,0 +1,46 @@
+  public function multiple() {
+    return TRUE;
+  }

This would should be in the annotation, and we should add a new test if we do in-fact need it.

jgrubb’s picture

hi, here's a patch based off of #26 with the annotation, I'll get to work on the test now.

jgrubb’s picture

Forgot to remove ::multiple() in the class.

jgrubb’s picture

Hey all, so the currently existing suite still passes, what else might need to have coverage here?

heddn’s picture

Add a unit test that tries to adjust a value into a multi-valued result and see if a following process plugin uses each individual value. Rather than on the original full string.

jgrubb’s picture

Hi, sorry but I have no idea what I'm doing here. I've got this going so far --

<?php
  /**
   * Test handle_multiples
   */

  public function testProcessesMultiples() {
    $configuration = [
      'delimiter' => '|',
      'process' => [
        'bar' => [
          'plugin' => 'machine_name',
          'source' => 'value'
        ]
      ]
    ];
    $this->plugin = new Explode($configuration, 'explode', [], $this->moduleHandler->reveal(), $this->migrationPlugin->reveal());
    $source = 'this|that|the other';
    $result = ['this', 'that', 'the_other'];

    $row = new Row(array(), array());

    $transformed_value = $this->plugin->transform($source, $this->migrateExecutable, $row, 'bar');
    $this->assertSame($result, $transformed_value);
  }

which fails --

Failed asserting that Array &0 (
    0 => 'this'
    1 => 'that'
    2 => 'the other'
) is identical to Array &0 (
    0 => 'this'
    1 => 'that'
    2 => 'the_other'
)

So obviously I'm missing how to configure this test to chain processes together. I've been in the IteratorTest for most of the afternoon and am not able to tease apart how to set this up correctly.

jgrubb’s picture

I'm sorry, conceptually I know what needs to be done but I have no idea how to actually implement this test (the setup of these pipelined transformations).

quietone’s picture

@jgrubb, for your test to pass the last item in the array 'the other' need to match. Try changing 'the other' to 'the_other' in $source. Also, instead of making a new row, you can use $this->row.

Can anyone provide a use case where multiple is needed? If so, what would a migration template look like that uses the multiple feature?

quietone’s picture

Status: Needs review » Needs work
esclapes’s picture

Looking at the docs, for ::multiple() and handle_multiple they have different goals.

In this case I would say ::multiple() needs to be implemented and return true because explode always returns an array and chained plugins in the pipeline need to know.

If the plugin should handle_multiple is a different question. Are we expecting an array of strings to be exploded?

Patch in #26 looks like the right approach to me.

jcnventura’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +migrate
FileSize
6.88 KB
5.96 KB

I fully agree with #36 from @esclapes. The correct code is to define multiple(), since this function outputs multiple values (and does _not_ handle them).

This patch extends #26 to include a limit 'field', so that this can be used without problem in limited multi-value fields.

I've simplified the unit test somewhat based on the simpler TestConcat, and added a simple version of the process chaining between explode and concat.

It might be useful to have a better migration simpletest where we can define a full migration, and chain these values properly, but that's outside the scope of this issue, since that simpletest would focus on the proper handling of the multiple() = TRUE and handles_multiple plugins.

benjy’s picture

+++ b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php
@@ -0,0 +1,112 @@
+class TestExplode extends Explode {
...
+  public function setDelimiter($delimiter) {
...
+  public function setLimit($limit) {

Do we need a stub class, public function __construct(array $configuration, $plugin_id, $plugin_definition) the parent constructor definition looks simple enough that we could just pass in the values and set the config at the same time? See ExtractTest for example.

Rest looks good, RTBC once we've removed the stub class for testing.

jcnventura’s picture

FileSize
3.52 KB
5.45 KB

Removing the stub classes as per #38 + minor drupalcs warning.

benjy’s picture

Status: Needs review » Reviewed & tested by the community

Great, looks good to me.

jennypanighetti’s picture

This is exactly what I need for an upcoming import.

If I want to implement this before this gets into dev (or, not using the latest dev), do I just need the latest patch in #39?

quietone’s picture

@jenstechs, yes. Make sure your version is the same as shown at the top of this issue.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 4e39681 and pushed to 8.0.x and 8.1.x as migrate is experimental and this new feature does not introduce any risk to non-experimental code. Thanks!

@heddn btw exception messages are not supposed to use the markup or translation tools at all. Anything that displays them has to (and does) handle this.

  • alexpott committed e740b70 on 8.1.x
    Issue #2575101 by quietone, jcnventura, jgrubb, hussainweb, chr.fritsch...

  • alexpott committed 4e39681 on 8.0.x
    Issue #2575101 by quietone, jcnventura, jgrubb, hussainweb, chr.fritsch...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

heddn’s picture

Version: 8.0.x-dev » 9.x-dev

Explode had its own docs already, where the configurable delimiter is documented. Migrate source CSV had a configurable delimiter also; as it's underling php CSV also supports it. Please check out on their respective handbook pages

RKopacz’s picture

Hi @heddn thanks for this info. It answers my question which I asked here. My only question is whether, in the config file, to use the syntax recommended in comment #3 above in the config file?.

maxocub’s picture

Version: 9.x-dev » 8.0.x-dev

Wrong version.