Problem/Motivation

#2671312: No default value for User langcode when migrating D7 users with no language requires a migration process plugin that returns a config value by default if there is no value to migrate (i.e. NULL).

Proposed resolution

The process plugin has already been written as part of #2671312: No default value for User langcode when migrating D7 users with no language, but it is recommended to separate it into it's own issue.

Remaining tasks

The plugin also needs tests.

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Jo Fitzgerald created an issue. See original summary.

jofitz’s picture

jofitz’s picture

Add test for plugin (including adding dependency injection to the plugin).

jofitz’s picture

Assigned: jofitz » Unassigned
benjy’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultConfigValue.php
    @@ -0,0 +1,59 @@
    +/**
    + * @file
    + * Contains \Drupal\migrate\Plugin\migrate\process\DefaultConfigValue.
    + */
    

    un-needed comment.

  2. +++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultConfigValue.php
    @@ -0,0 +1,59 @@
    + * This plugin sets missing values on the destination using a value from configuration.
    

    > 80 chars.

  3. +++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultConfigValue.php
    @@ -0,0 +1,59 @@
    +  protected $container;
    +
    +  /**
    +   * Set the container.
    +   *
    +   * @param $container
    +   */
    +  public function setContainer($container) {
    ...
    +  /**
    +   * Get the container.
    +   *
    +   * @return null|\Symfony\Component\DependencyInjection\ContainerInterface
    +   */
    +  public function getContainer() {
    +    if (!isset($this->container)) {
    +      $this->container = \Drupal::getContainer();
    +    }
    +    return $this->container;
    +  }
    

    This is wrong, we need to use ContainerInjectionInterface and inject just the dependencies we need. See Drupal\block\Plugin\migrate\process\BlockVisibility for an example.

  4. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,109 @@
    +/**
    + * @file
    + * Contains \Drupal\Tests\migrate\Unit\process\DefaultConfigTest.
    + */
    

    un-needed comment

jofitz’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
7.06 KB
5.2 KB

Addressed comments in @benjy's code review.

benjy’s picture

Patch looks good, just one small nitpick and then RTBC.

+++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultConfigValue.php
@@ -0,0 +1,60 @@
+  public function __construct(ConfigFactory $config_factory, array $configuration, $plugin_id, $plugin_definition) {
...
+      $container->get('config.factory'),

Normally, we inject new parameters at the end.

jofitz’s picture

Made corrections according to code review.

Status: Needs review » Needs work

The last submitted patch, 8: defaultconfigvalue_plugin-2752281-8.patch, failed testing.

jofitz’s picture

Status: Needs work » Needs review
FileSize
1.68 KB
5.2 KB
benjy’s picture

+++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
@@ -0,0 +1,93 @@
+  protected function assertProcess($original_value, $expected_value) {
...
+  protected function getConfigFactory() {

Missing comments, bit pointless I know but we need them for phpcs.

jofitz’s picture

Added missing comments on methods.

benjy’s picture

Sorry to keep pushing this back on docs but the committers won't commit it unless it's right as there is an effort to make Drupal core pass an automated coding standard check. It's worthwhile making yourself familiar with the standards: https://www.drupal.org/coding-standards

+++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
@@ -0,0 +1,104 @@
+   * @param $original_value
+   * @param $expected_value

These need comments and types.

+++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
@@ -0,0 +1,104 @@
+   * @return \PHPUnit_Framework_MockObject_MockObject

This needs a comment.

jofitz’s picture

No problem, I must make sure I get these things right. Comments improved.

benjy’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for sticking with it, I think this is ready. I'd be happy for this to go in core although I can't ever see it been used in the upgrade path from d6/d7 it will be useful for others using the generic Migrate API.

+++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
@@ -0,0 +1,107 @@
+   * @param string|boolean|NULL $original_value
...
+   * @param string|boolean $expected_value

This could probably be fixed on commit per: https://www.drupal.org/coding-standards/docs#types it needs to be bool and not boolean.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 14: defaultconfigvalue_plugin-2752281-14.patch, failed testing.

jofitz’s picture

Status: Needs work » Reviewed & tested by the community

Now passing tests again, assume there were testbot issues. Setting back to RTBC.

@benjy RE use in d6/d7 upgrade path: I have another patch, #2671312: No default value for User langcode when migrating D7 users with no language, waiting on this one before it can progress.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
diff --git a/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
index 3e213b5..344809f 100644
--- a/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
+++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
@@ -66,9 +66,9 @@ public function testWithoutStrict() {
   /**
    * Carry out a process and assert that the correct value is returned.
    *
-   * @param string|boolean|NULL $original_value
+   * @param string|bool|NULL $original_value
    *   The original, pre-process value.
-   * @param string|boolean $expected_value
+   * @param string|bool $expected_value
    *   The expected post-process value.
    */
   protected function assertProcess($original_value, $expected_value) {
  1. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,107 @@
    +  /**
    +   * Test DefaultConfigValue works with strict set.
    +   */
    +  public function testWithStrict() {
    +    $this->assertProcess(NULL, 'foo');
    +    $this->assertProcess(TRUE, TRUE);
    +    $this->assertProcess(FALSE, FALSE);
    +    $this->assertProcess('bar', 'bar');
    +  }
    

    This type of test looks like it could use the @dataProvider pattern.

  2. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,107 @@
    +    $configuration = [
    +      'config_object' => $this->configObjectId,
    +      'config_key' => $this->configKeyId,
    +    ];
    +    $this->plugin = new DefaultConfigValue($configuration, 'default_config_value', [], $this->getConfigFactory());
    

    This undoes what setUp is doing - not sure the setUp method is necessary should just be part of the test that needs it.

  3. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,107 @@
    +    $this->assertProcess(NULL, 'foo');
    +    $this->assertProcess(TRUE, TRUE);
    +    $this->assertProcess(FALSE, 'foo');
    +    $this->assertProcess('bar', 'bar');
    

    Again could use the @dataProvider pattern.

  4. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,107 @@
    +  /**
    +   * Provide a mock ConfigFactory.
    +   *
    +   * @return \PHPUnit_Framework_MockObject_MockObject
    +   *   The mocked Config Factory.
    +   */
    +  protected function getConfigFactory() {
    +    $config_data = $this->getMockBuilder('Drupal\Core\Config\ImmutableConfig')
    +      ->disableOriginalConstructor()
    +      ->setMethods(array('get'))
    +      ->getMock();
    +    $config_data->expects($this->any())
    +      ->method('get')
    +      ->with($this->equalTo($this->configKeyId))
    +      ->will($this->returnValue('foo'));
    +
    +    $confing_factory = $this->getMockBuilder('Drupal\Core\Config\ConfigFactory')
    +      ->disableOriginalConstructor()
    +      ->setMethods(array('get'))
    +      ->getMock();
    +    $confing_factory->expects($this->any())
    +      ->method('get')
    +      ->with($this->equalTo($this->configObjectId))
    +      ->will($this->returnValue($config_data));
    +
    +    return $confing_factory;
    +  }
    

    I think this could be replaced with $this->getConfigFactoryStub() in setUp()

alexpott’s picture

Status: Needs work » Needs review
FileSize
6.06 KB
4.19 KB

Here's a patch showing what I'm on about. Also discovered that the process plugin was typehinting on ConfigFactory and not ConfigFactoryInterface (as it should be).

quietone’s picture

Issue tags: -migrate-d7-d8
  1. +++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultConfigValue.php
    @@ -0,0 +1,60 @@
    +    if (!empty($this->configuration['strict'])) {
    

    Why is there both a strict and not strict version? Can you give an example where each is needed?

  2. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,62 @@
    +  public function testTransform($value, $expected, array $configuration) {
    

    Does this need @param in the docblock?

  3. +++ b/core/modules/migrate/tests/src/Unit/process/DefaultConfigValueTest.php
    @@ -0,0 +1,62 @@
    +  public function providerTestTransform() {
    

    I think it would be clearer to also include tests with different results. That is, have a source of empty string or 0.

Removed the tag since this is not D7->D8 specific.

chx’s picture

The more of think of this issue the more it unravels.

  1. At least the config value should be a process plugin in itself and then either default_value needs to be amended or we need to add a new plugin default_value_from_property. Welding these two plugins into one looks like an owie (which quietone already started to feel when asking about strict -- I would bet it's just a copy-paste of DefaultValue::transform() and that's it).
  2. The process plugin which gets a default from a property needs the Get plugin injected so that you can use both source and destination properties freely.
  3. Then of course StaticMap has a default value but it's slightly different semantics because it's not triggered by any kind of emptiness but by failing to map. Still, it could reuse the default value plugin and for this reason expanding the capabilities of DefaultValue and injecting it into StaticMap is preferred.
  4. And then we didn't even begin to think that the new process plugin which loads from config is not a process plugin but rather a funnier kind of source constant so I would prefer it to be a value adding plugin from #2543552: Modernize migration source plugins.
jofitz’s picture

Response to @quietone:

  1. The strict version allows false-y values, while the not strict version treats them the same as it would NULL. Sometimes you may only wish to overwrite NULL values with a config value (strict), other times you might consider all false-y values to be 'missing' and therefore need replacing with a value from config (not strict). This functionality is recycled from the DefaultValue plugin on which this plugin is based.
  2. I have added the missing @params
  3. As suggested, I have added the additional testing of empty string and 0.

I will address @chx's remarks in another comment.

jofitz’s picture

FileSize
1.72 KB

Added previously malformed interdiff.

jofitz’s picture

I think @chx makes many valid points, I can see the reasoning and I agree with most if not all of the assertions; it might be preferable to separate config_value into its own plugin & somehow combine that with default_value and I am interested in the concept of a kind of source constant.

On the other hand, the absence of the functionality provided by this plugin has been delaying the correction of a fundamental flaw in migration (#2671312: No default value for User langcode when migrating D7 users with no language) since February. In turn, that flaw is delaying at least one issue (#2563649: Migrations: Metatag-D7 basic entities) and impacting other (core) issues (e.g. #2673960: Unable to migrate D7 User cck fields & #2674152: D7 User fields content not migrating first time).

As a compromise, I suggest committing this patch as "deprecated" while simultaneously opening another ticket to work towards an ideal solution, as requested by @chx. This will allow other dependent work to continue, while working towards an eventual solution acceptable to all.

jofitz’s picture

Would someone mind responding to my suggestion, please? It's been a fortnight and it would be good to get this (and the related issues) moving. I am open to alternative suggestions.

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

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

phenaproxima’s picture

Version: 8.2.x-dev » 8.3.x-dev

I am of two minds about this, but because it's basically an API improvement at this point and not (to my knowledge) blocking anything critical, I find myself leaning more in the direction of what @chx proposes.

I would suggest that we change DefaultValue to support this behavior. Let's add two new configuration options: from_property and from_config.

from_property would utilize an injected Get plugin instance and could therefore be used this way:

# Gets a source row property
from_property: some_source_property
# Gets a destination row property
from_property: '@some_destination_property'

from_config would use an injected ConfigFactoryInterface and would be configured thusly:

from_config: 'system.site:configuration.property.path'

What do y'all think?

jofitz’s picture

Now that #2671312: No default value for User langcode when migrating D7 users with no language has been closed I don't think this change is necessary, but I'm happy to leave this open if there are other applications.

phenaproxima’s picture

Status: Needs review » Closed (won't fix)

If you think this change is obviated, then we can wontfix this. Thank you! :)