Problem/Motivation

Following the proposed solution for #2842222: D7 Plain text fields incorrectly migrated to D8 as Text (formatted) as outlined in #107, we need a plugin to process the field type other than the current static map.

Proposed resolution

Write a process plugin called process_field which will take two configuration: the source field type and the name of a method to be called.
The plugin will then instantiate the appropriate Field plugin based on the given field type and call the given method on the instantiated Field plugin, which will return the processed destination field type.

Remaining tasks

Write a patch, Review

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

maxocub created an issue. See original summary.

maxocub’s picture

Status: Active » Needs review
FileSize
5.18 KB

Here's a first patch.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

maxocub’s picture

jofitz’s picture

Status: Needs review » Reviewed & tested by the community

Applied the patch to my local build and stepped through MigrateFieldTest.php as the prime example of how this should work. The new plugin runs along the established lines of the "field_type" plugin and works as expected.

The changes added in #5 makes sense to speed up the acceptance of this patch.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/field/migration_templates/d7_field.yml
new file mode 100644
index 0000000..9c7571b

index 0000000..9c7571b
--- /dev/null

--- /dev/null
+++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php

+++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
+++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
@@ -0,0 +1,140 @@

@@ -0,0 +1,140 @@
+<?php
+

There's no explicit test coverage for this, shouldn't there be some?

maxocub’s picture

Status: Needs review » Needs work

Yes indeed there should be, I'll try to add some.

maxocub’s picture

Status: Needs work » Needs review
FileSize
11.4 KB
4.5 KB

Here's a Unit test.

Status: Needs review » Needs work

The last submitted patch, 9: 2893061-9.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

maxocub’s picture

Status: Needs work » Needs review
FileSize
11.44 KB
3.19 KB

I corrected the coding standard errors and I added @group legacy to the test so it won't fail due to the deprecation warning for MigrateCckFieldPluginManagerInterface.

I'm not sure if it's the best way to make the test pass.
I could also just remove the cck code from the plugin now, since it's deprecated anyway, or we can deal with cleaning this up in #2897593: Cleanup further references to CCK.

What do you think?

catch’s picture

Status: Needs review » Needs work

All depends on what's going to rely on the deprecated code, which I'm not entirely sure about at this point tbh. I'd lean towards leaving it in unless it's already effectively dead code.

Just committed #2901851: Replace the static map in the d7_field migration by field plugins by the way but afraid this means another re-roll here.

jofitz’s picture

Status: Needs work » Needs review
FileSize
10.49 KB

As mentioned in #12, a re-roll was required.

Status: Needs review » Needs work

The last submitted patch, 13: 2893061-13.patch, failed testing. View results

jofitz’s picture

Status: Needs work » Needs review

Test failures are unrelated to this issue, they occur even when the patch is not applied (which is concerning...)

maxocub’s picture

@catch, re #12:
In core, it is dead code, but maybe some contrib/custom module have created there own cckfield plugins and have not yet updated them to field plugins. I guess we better leave the cck code for now and deal with it in #2897593: Cleanup further references to CCK.

xjm’s picture

Issue tags: +Triaged D8 critical

@cilefen, @effulgentsia, @Cottser and I discussed this and agreed it makes sense to handle it as a critical as a blocker for #2842222: D7 Plain text fields incorrectly migrated to D8 as Text (formatted). While the title doesn't really describe a critical issue on its own, and we don't typically hard-block criticals on API additions, it's helpful here because (as @catch mentioned) we lost track of an earlier issue in the chain and the rest have gone quickly once we were able to promote them for visibility. And, as @effulgentsia mentioned, there isn't really a good solution for this currently, so there's not some hotfix we should pursue instead.

Thanks for the ongoing work on this chain of issues!

phenaproxima’s picture

Status: Needs review » Needs work

This is an excellent patch. First-class work all around! It wouldn't be me if I didn't raise a few questions, though :)

  1. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -0,0 +1,140 @@
    + * If no field plugin for the given field type is found, the source field type
    + * will be returned back to the process pipeline.
    

    I think we should probably return NULL instead. So that, say, one could skip_on_empty. Either that, or throw an exception? If no appropriate field plugin is found, I think it's safer to consider that an uncertain condition that requires developer intervention.

  2. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -0,0 +1,140 @@
    +   * The migration object.
    

    Nit: Should match the constructor parameter.

  3. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -0,0 +1,140 @@
    +    $field_type = is_array($value) ? $value[0] : $value;
    

    Let's change $value[0] to reset($value), just in case $value is associative.

  4. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -0,0 +1,140 @@
    +      $plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, [], $this->migration);
    +      $plugin_instance = $this->fieldPluginManager->createInstance($plugin_id, [], $this->migration);
    +      if (!method_exists($plugin_instance, $method) || !is_callable([$plugin_instance, $method])) {
    +        throw new MigrateException('The specified method does not exists or is not callable.');
    +      }
    +      return call_user_func_array([$plugin_instance, $method], [$row]);
    

    Can we abstract this a bit as a helper method that accepts the plugin manager to try, rather than repeat the code entirely?

  5. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,120 @@
    + * @group legacy
    

    Interesting, I've never seen this test group before in core. Is it a new thing?

  6. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,120 @@
    +class ProcessFieldTest extends MigrateProcessTestCase {
    

    I think we should refactor this test class to use the data provider pattern, which would make it easier for us to test new methods, new combinations and types of arguments, etc.

  7. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,120 @@
    +    $this->cckFieldManager = $this->getMock(MigrateCckFieldPluginManagerInterface::class);
    +    $this->fieldInstance = $this->getMock(MigrateFieldInterface::class);
    +    $this->fieldManager = $this->getMock(MigrateFieldPluginManagerInterface::class);
    +    $this->migration = $this->getMock(MigrationInterface::class);
    

    I would prefer if we used Prophecy for all this, but I'm not married to it.

    Also, for clarity, can $this->fieldInstance be renamed to $this->fieldPlugin?

catch’s picture

On #5 it allows the test to pass without warnings despite it using deprecated code. https://www.drupal.org/core/deprecation

catch’s picture

Version: 8.5.x-dev » 8.4.x-dev

Moving back to 8.4.x since it's eligible.

maxocub’s picture

Status: Needs work » Needs review
FileSize
9.97 KB
11.2 KB

Thanks for the review @phenaproxima!

1. It now returns NULL.
2. Done.
3. Done.
4. Done.
5. See #19.
6. Added a data provider.
7. Changed for Prophecy.

phenaproxima’s picture

Status: Needs review » Needs work

All the karma unto @maxocub. You are a rock star.

A few more things jumped out at me, but they should all be reasonably easy fixes. They basically come down to documentation.

  1. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -115,26 +114,37 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
    +  /**
    +   * Instantiate a field plugin and call a method on it.
    +   *
    +   * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
    +   *   The field plugin manager.
    +   * @param string $field_type
    +   *   The field type for which to get the field plugin.
    +   * @param string $method
    +   *   The method to call on the field plugin.
    +   * @param \Drupal\migrate\Row $row
    +   *   The row from the source to process.
    +   */
    +  protected function callMethodOnFieldPlugin(MigrateFieldPluginManagerInterface $field_plugin_manager, $field_type, $method, Row $row) {
    

    Missing @return documentation.

  2. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -115,26 +114,37 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
    +    if (!method_exists($plugin_instance, $method) || !is_callable([$plugin_instance, $method])) {
    

    Is the method_exists() call necessary? Won't is_callable() do essentially the same thing?

  1. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,118 @@
    +    if ($migrate_exception) {
    +      $this->setExpectedException(MigrateException::class);
    +    }
    

    Ideally, $migrate_exception would be the full or partial text of the exception message, and we would assert that. ($this->setExpectedExceptionMessage() or something)

  2. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,118 @@
    +    return [
    +      [
    +        'getFieldType',
    +        'foo',
    +        'bar',
    +        FALSE,
    +        FALSE,
    +      ],
    +      [
    +        'getFieldFormatterMap',
    +        'foo',
    +        ['foo' => 'bar'],
    +        FALSE,
    +        FALSE,
    +      ],
    +      [
    +        'getFieldWidgetMap',
    +        'foo',
    +        ['foo' => 'bar'],
    +        FALSE,
    +        FALSE,
    +      ],
    +      [
    +        '',
    +        '',
    +        '',
    +        TRUE,
    +        FALSE,
    +      ],
    +      [
    +        'getFieldType',
    +        'foo',
    +        NULL,
    +        FALSE,
    +        TRUE,
    +      ],
    +    ];
    

    Can you add some comments here to briefly explain each scenario we're running, what we are doing in each, and what we expect to happen? I <3 the data provider pattern, but it can sometimes obfuscate our intentions.

maxocub’s picture

Status: Needs work » Needs review
FileSize
10.38 KB
3.25 KB

Fast review = fast new patch.

1. Done.
2. You're right, it's superfluous.
3. setExpectedExceptionMessage() doesn't seem to exists on this test class, but I added the expected message in the setExpectedException() call.
4. Done.

maxocub’s picture

Changed the $migrate_exception variable type from bool to string, based on a discussion with phenaproxima on irc.
Also added some @params on the test method.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

This looks beautiful and should pass Drupal CI. RTBC once it does.

heddn’s picture

Status: Reviewed & tested by the community » Needs work

A couple questions/comments.

  1. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -0,0 +1,153 @@
    +    $field_type = is_array($value) ? reset($value) : $value;
    

    In what cases could this be an array? Are we assuming the first array value is the field_type? That seems like magic and bad.

    I also don't see a test for this scenario, so that maybe would clear up what we're trying to do here.

  2. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -0,0 +1,123 @@
    +        'getFieldType',
    +        'foo',
    +        'bar',
    +        FALSE,
    +        FALSE,
    

    It would be helpful if the keys on this were the variable names being passed into the test function.

maxocub’s picture

Status: Needs work » Needs review
FileSize
11.26 KB
4.39 KB

1. @heddn and I decided on irc that we should not expect an array, only a string representing the field type. If it's not a string, we throw an exception. I added test data for that scenario.
2. Done.

heddn’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/field/src/Plugin/migrate/process/ProcessField.php
    @@ -106,7 +106,9 @@ public static function create(ContainerInterface $container, array $configuratio
    +    if (!is_string($value)) {
    

    I think this should be is_scalar. See https://3v4l.org/lVPqC

  2. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/ProcessFieldTest.php
    @@ -56,7 +56,7 @@ protected function setUp() {
    +  public function testTransform($method, $field_type, $expected_value, $migrate_exception = '', $plugin_not_found = FALSE) {
    

    This should be $value instead of $field_type, no?

maxocub’s picture

Status: Needs work » Needs review
FileSize
11.22 KB
4.13 KB

1. I don't think so, a field type cannot be an integer, and is_scalar return true for an integer.
2. Changed $field_type to $value.

heddn’s picture

Status: Needs review » Reviewed & tested by the community

All feedback addressed. Back to RTBC if tests pass.

  • larowlan committed bd2227e on 8.5.x
    Issue #2893061 by maxocub, Jo Fitzgerald, heddn, phenaproxima: Create a...

  • larowlan committed 63bd90d on 8.4.x
    Issue #2893061 by maxocub, Jo Fitzgerald, heddn, phenaproxima: Create a...

larowlan credited larowlan.

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed as bd2227e and pushed to 8.5.x.

Cherry-picked as 63bd90d and pushed to 8.4.x

Great work!

larowlan’s picture

Issue tags: +8.4 release notes

Oh - can we get a change record here?

maxocub’s picture

@larowlan: Sure, I'll add one. Thanks for commiting this!

maxocub’s picture

I created a change record. I left it in draft even though the issue is already fixed so someone can review it, correct it if needed, and publish it.

larowlan’s picture

published that

maxocub’s picture

maxocub’s picture

xjm’s picture

Issue tags: -8.4 release notes

I don't think we need to mention this issue specifically, just the one that it unblocked which is the big win. Thanks all!

Status: Fixed » Closed (fixed)

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