Problem/Motivation

Discovered via #2814953: Migrate Drupal 7 node/user reference fields, but is a long pre-existing bug.

#2814953-96: Migrate Drupal 7 node/user reference fields contains:

+++ b/core/modules/field/src/Plugin/migrate/process/d7/FieldInstanceSettings.php
@@ -65,6 +65,43 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
+        'target_bundles' => array_filter($field_data['settings']['referenceable_types'] ?? []),

Yet … field_field_config_presave() does this:

  // In case we removed all the target bundles allowed by the field in
  // EntityReferenceItem::onDependencyRemoval() or field_entity_bundle_delete()
  // we have to log a critical message because the field will not function
  // correctly anymore.
  $handler_settings = $field->getSetting('handler_settings');
  if (isset($handler_settings['target_bundles']) && $handler_settings['target_bundles'] === []) {
    \Drupal::logger('entity_reference')->critical('The %field_name entity reference field (entity_type: %entity_type, bundle: %bundle) no longer has any valid bundle it can reference. The field is not working correctly anymore and has to be adjusted.', [
      '%field_name' => $field->getName(),
      '%entity_type' => $field->getTargetEntityTypeId(),
      '%bundle' => $field->getTargetBundle(),
    ]);
  }

IOW, target_bundles must either be NULL or if it's an array, it must be non-empty. Otherwise all references will be refused!

Steps to reproduce

node_reference field without referenceable_types setting triggers something like this during the migration:

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3198732

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

StatusFileSize
new377.14 KB

This happens for two content types on training.acquia.com, both of which do not specify referenceable_types and hence get the [] fallback, which is wrong as explained in the issue summary.

wim leers’s picture

Issue summary: View changes
quietone’s picture

Are we sure it should be NULL? On D9.2.x when configuring a content reference field, the 'Content types' is a required field. Maybe it should be an array of all the referenceable_types available on the source site. I mean NULL may work but what is shown in the UI would not be.

matroskeen’s picture

If missing referenceable_types property means "allow all bundles", then NULL is fine. Otherwise, it might end up with some unexpected behavior.

Let's say on the source site the reference field allowed bundle "A" to be referenced.
However, on the destination, bundle "A" is not available at the moment.

Given that, the reference field might be empty, because we can't just "allow all", can we?

wim leers’s picture

Maybe it should be an array of all the referenceable_types available on the source site.

Let's say on the source site the reference field allowed bundle "A" to be referenced.
However, on the destination, bundle "A" is not available at the moment.

All of this, plus the fact that bundles may be renamed on the destination, leads me to one obvious suggestion: allow all bundles on the destination site. That's the intent of the setting on the source, and it's the equivalent correct configuration on the destination.

IOW: I proposed NULL because it's the simplest possible solution, and the migration process plugin already does that for entityreference source field type migrations. But you're right that it could (should) be even better. That just requires more work.

quietone’s picture

Status: Active » Needs review
StatusFileSize
new3.39 KB
new3.29 KB
new6.85 KB

I tested on D7 and when no content types are selected it is not possible to enter a value in the node reference field. On D8 when no content types are selected, when target_bundles = NULL, it is possible to enter a value in a node reference field. If I haven't made a mistake there then we can't replicate the behaviour.

I had made a patch that uses NULL, as suggested, and the process plugin now does a migrate lookup to get the destination values for the node_types.

Status: Needs review » Needs work

The last submitted patch, 7: 3198732-7.patch, failed testing. View results

wim leers’s picture

FYI @huzooka has had to tackle similar problems in https://www.drupal.org/project/media_migration — I think the relevant code is at \Drupal\media_migration\Plugin\migrate\process\MediaImageFieldInstanceSettings::transform(), but I'll let him confirm and elaborate 🤓

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new5.41 KB
new7.95 KB
new8.97 KB

Use the process plugin test instead of a migration test and change it to use a data provider. No changes to the fix.

The last submitted patch, 10: 3198732--10-fail.patch, failed testing. View results

wim leers’s picture

Amazing progress here! 🥳

huzooka’s picture

With patch #10, d7_field_instance migration need a hard dependency on d7_node_type.

wim leers’s picture

#13: good point. In fact, it will need a required dependency on every migration that targets a config entity type that represents the bundles for a particular destination entity type. Not just node_type. So also d7_taxonomy_vocabulary in core, but more still in contrib.

Ideally, we can automatically compute this. Otherwise many contrib migrations (for contrib entity types) are likely to get this wrong.

wim leers’s picture

Status: Needs review » Needs work
+++ b/core/modules/field/src/Plugin/migrate/process/d7/FieldInstanceSettings.php
@@ -68,12 +120,25 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
+      $target_bundles = empty($target_bundles) ? NULL : $target_bundles;

NULL should be replaced with an array listing all node type IDs, otherwise it's still invalid.

wim leers’s picture

To follow up on #15: I wanted to be 100% certain about this.

So I went ahead and created a new entity_reference fields with node as the target entity type. Upon doing so, I end, the following field.field.node.course.field_test.yml was generated:

uuid: da867862-23c0-4ad6-8abf-ce11ef3b9bb4
langcode: en
status: true
dependencies:
  config:
    - field.storage.node.field_test
    - node.type.course
id: node.course.field_test
field_name: field_test
entity_type: node
bundle: course
label: test
description: ''
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  handler: 'default:node'
  handler_settings: {  }
field_type: entity_reference

… which does match the current migration system behavior.

However, if you try to save this at /admin/structure/types/manage/course/fields/node.course.field_test, then you'll get this:

The form validation logic does the right thing: it matches the field_field_config_presave() logic (which was introduced in #2925913: EntityReferenceItem logs critical errors in onDependencyRemoval()).

The problem is that first field.storage.node.field_test.yml gets generated, and a placeholder field.field.node.course.field_test.yml gets generated. You're supposed to click Save settings on the above screenshot. But nothing is forcing you to. But that is a bug/weakness in Field UI (for having such a brittle UX) and in Field API (for not warning about this, anywhere, not even on the Status Report, where it happily reports Entity/field definitions: Up to date 😔).

Overall: a pretty messy situation. But at least for the migration system, the solution is pretty simple (will work on that now). For Field UI/Field API the solution is less clear. And definitely out of scope.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new9.07 KB

This fixes #15 + #16.

wim leers’s picture

StatusFileSize
new1.46 KB

Interdiff for #17.

wim leers’s picture

Similar (but different) problem for user_reference fields:

The problems here are that the roles that are being set are labels instead of the config ID — we need to use the processed role ID (i.e. it must undergo the same processing as d7_user_role.yml specifies).

Furthermore, in D7 it's possible to configure it to only allow the authenticated user. But … that is expressly forbidden in D8: you have to either pick a specific user role. Besides, the equivalent of "only authenticated" is "not anonymous". And for that, D8|9 gained an explicit setting: include_anonymous (since #2434697: Remove UserAutocompleteController).

So, incorporating all newfound processing knowledge that in this interdiff.

Before
uuid: fd86347a-949d-40f9-a0ce-f16d35f612da
langcode: en
status: true
dependencies:
  config:
    - field.storage.node.field_candidate
    - node.type.payment_exam_info
id: node.payment_exam_info.field_candidate
field_name: field_candidate
entity_type: node
bundle: payment_exam_info
label: Candidate
description: ''
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  handler: 'default:user'
  handler_settings:
    include_anonymous: true
    filter:
      type: role
      role:
        'authenticated user': 'authenticated user'
    sort:
      field: _none
      direction: ASC
    auto_create: false
field_type: entity_reference

After
uuid: a7a1d425-09d0-47ff-9c66-0d89b2eacf28
langcode: en
status: true
dependencies:
  config:
    - field.storage.node.field_candidate
    - node.type.payment_exam_info
id: node.payment_exam_info.field_candidate
field_name: field_candidate
entity_type: node
bundle: payment_exam_info
label: Candidate
description: ''
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  handler: 'default:user'
  handler_settings:
    include_anonymous: false
    filter:
      type: _none
    sort:
      field: _none
      direction: ASC
    auto_create: false
field_type: entity_reference

The last submitted patch, 17: 3198732-17.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 19: 3198732-19.patch, failed testing. View results

wim leers’s picture

  1. +++ b/core/modules/field/src/Plugin/migrate/process/d7/FieldInstanceSettings.php
    @@ -68,12 +122,28 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
    +        // @see field_field_config_presave()
    +        $target_bundles = array_keys(NodeType::loadMultiple());
    

    To get a [article ⇒ article, blog ⇒ blog]-style result here, we should use array_combine().

  2. +++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/d7/FieldInstanceSettingsTest.php
    @@ -18,23 +19,103 @@ class FieldInstanceSettingsTest extends MigrateTestCase {
    -  public function testTransformImageSettings() {
    +  public function testTransformImageSettings($type, $instance_settings, $widget_settings, $field_definition, $lookup_result, $expected_results) {
    

    Running these tests fails locally for me 😬

    1) Drupal\Tests\field\Unit\Plugin\migrate\process\d7\FieldInstanceSettingsTest::testTransformImageSettings with data set "node_reference no content types" ('node_reference', array(), array('node_reference'), array('a:1:{s:8:"settings";a:1:{s:19...:0;}}}'), array(), array('default:node', array(array('_none', 'ASC'), null)))
    Drupal\Core\DependencyInjection\ContainerNotInitializedException: \Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container.
    

    Between this and #3197324: Exception trace cannot be serialized because of closure, running core migration tests is … mildly maddening 😅 Pointers welcome!

  3. The additional failures in #19 indicate that we have working test coverage for user_reference, but the fact that include_anonymous remains TRUE suggests that we really need to fix #13 too.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

srishtiiee’s picture

StatusFileSize
new16.32 KB
new10.42 KB
wim leers’s picture

Thanks, @srishti.bankar, for addressing those first 2 failures in #19, and oddly enough now there's a different set of failures! 😳

Will re-investigate

quietone’s picture

@srishti.bankar, thanks for the patch. There are changes to the source Drupal 7 fixture in the patch, what are those changes doing? Why are they needed?

The issue summary needs an update. @Wim Leers, can you summarize your analysis in the IS? Particularly the differences between D7 and beyond?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

narendrar’s picture

StatusFileSize
new10.14 KB

Patch #19 rerolled to 9.3.x

However while creating interdiff, I get below error and interdiff file was empty.

1 out of 3 hunks FAILED -- saving rejects to file /var/folders/1_/z9gwdk392dxdr23sq3cl1ryh0000gn/T//interdiff-1.3J8i6W.rej
interdiff: Error applying patch1 to reconstructed file

Hence attaching rerolled patch only.

wim leers’s picture

You omitted the changes to MigrateDrupal7TestBase.php and the fixture, @narendraR. Why?

narendrar’s picture

The test changes in #24 are not included in #28. #28 is rerolled over #19.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
Related issues: +#3319582: Fix calls to methods with too many parameters passed in
StatusFileSize
new9.72 KB

#3319582: Fix calls to methods with too many parameters passed in conflicted with this, but also allows slightly simplifying this 👍

Status: Needs review » Needs work

The last submitted patch, 33: 3198732-33.patch, failed testing. View results

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

ovilla’s picture

StatusFileSize
new9.79 KB

This is a reroll of #33 for Drupal 10.3

acbramley’s picture

This came up in BSI. It needs a reroll on to an MR against 11.x

rpayanm made their first commit to this issue’s fork.

jofitz made their first commit to this issue’s fork.

jofitz’s picture

Issue tags: -Needs reroll

My commit fixes an issue in the unit test, but highlights another issue; what is the expected value of target_bundles in the 'node_reference no content types' test? My understanding is that it should be NULL, therefore we need to reinstate $target_bundles = empty($target_bundles) ? NULL : $target_bundles; while retaining the preceding else condition.

@wim leers if you can dredge this from your memory then I would appreciate your opinion, thanks.

(no longer needs reroll)

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.