Problem/Motivation

Content entities don't require having bundle support. For example – User entity doesn't have it out of the box. This means, this entity type doesn't have value for bundle entity key. In that cases, entities by itself fallback to entity type id for bundle name. For User, it will be user bundle.

\Drupal\Core\Entity\ContentEntityBase::__construct$this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId;

For the main migration, it solved by providing configuration default_bundle (\Drupal\migrate\Plugin\migrate\destination\Entity::getBundle).

But for stub entity migrations, this situation doesn't handled at all and this escalates very quickly.

When creating stub for entity without bundle, this method fails to provide proper handling of this situation: \Drupal\migrate\Plugin\migrate\destination\EntityContentBase::processStubRow. It tries to get bundle from entity key $bundle_key = $this->getKey('bundle');, but it doesn't exist for such entities, and then it collected fields for this entity + bundle combination:

    $fields = $this->entityFieldManager
      ->getFieldDefinitions($this->storage->getEntityTypeId(), $row->getDestinationProperty($bundle_key));

. The problem here is $row->getDestinationProperty($bundle_key) returns NULL and passes it as a $bundle value for \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions which doesn allows that:

\Drupal\Core\Entity\EntityFieldManagerInterface::getFieldDefinitions:

  /**
   * Gets the field definitions for a specific bundle.
   *
   * @param string $entity_type_id
   *   The entity type ID. Only entity types that implement
   *   \Drupal\Core\Entity\FieldableEntityInterface are supported.
   * @param string $bundle
   *   The bundle.
   *
   * @return \Drupal\Core\Field\FieldDefinitionInterface[]
   *   The array of field definitions for the bundle, keyed by field name.
   */
  public function getFieldDefinitions($entity_type_id, $bundle);

Then it calls hooks like hook_entity_bundle_field_info() and other code that also expects that $bundle is exists. So the code that relies on bundle value during stub migration will fail, which will lead to broken migration.

Steps to reproduce

Run migration with migration_lookup that creates stubs for entities without bundle like User and provide hook_entity_bundle_field_info() with types string $bundle and declare(strict_types=1);.

Proposed resolution

Use same logic used by content entities itself – provide fallback bundle ID equals entity type ID $bundle_key = $this->getKey('bundle') ?: $this->storage->getEntityTypeId();

Remaining tasks

  • Check tests
  • Fix it

Comments

Niklan created an issue. See original summary.

niklan’s picture

I didn't come up with any better idea than just decorate entity_field.manager and throw exception if $bundle is NULL.

niklan’s picture

Status: Active » Needs review
StatusFileSize
new4.05 KB
new726 bytes

This patch with a fix.

niklan’s picture

Title: Entity stubs doesn't follows fallback logic from entities and » Entity stubs doesn't follows fallback logic from entities and leads to a broken migration
niklan’s picture

Issue summary: View changes

Status: Needs review » Needs work
niklan’s picture

Status: Needs work » Needs review

Settings needs review, because I don't think the fail

1) Drupal\Tests\settings_tray\FunctionalJavascript\SettingsTrayBlockFormTest::testEditModeEnableDisable
Failed asserting that false is true.

in both patches related to them in any way.

smustgrave’s picture

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

Thank you for a super clear issue summary.

The proposed solution lines up with the patch.
#2 shows the issue

Triggering for 10.1

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

No failures.

Status: Reviewed & tested by the community » Needs work
niklan’s picture

Status: Needs work » Reviewed & tested by the community

Tests passes again, the error was not related to this issue.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php
@@ -304,4 +308,65 @@ public function testStubRows() {
+    $decorated_entity_field_manager = new class($entity_field_manager) implements EntityFieldManagerInterface {
+
+      public function __construct(
+        protected EntityFieldManagerInterface $entityFieldManager,
+      ) {}
+
+      public function getFieldDefinitions($entity_type_id, $bundle) {
+        if (\is_null($bundle)) {
+          throw new \Exception("Bundle value shouldn't be NULL.");
+        }
+
+        return $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id, $bundle);
+      }
+
+      public function getBaseFieldDefinitions($entity_type_id) {
+        return $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
+      }
+
+      public function getFieldStorageDefinitions($entity_type_id) {
+        return $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
+      }
+
+      public function getFieldMap() {
+        return $this->entityFieldManager->getFieldMap();
+      }
+
+      public function setFieldMap(array $field_map) {
+        return $this->entityFieldManager->setFieldMap($field_map);
+      }
+
+      public function getFieldMapByFieldType($field_type) {
+        return $this->entityFieldManager->getFieldMapByFieldType($field_type);
+      }
+
+      public function clearCachedFieldDefinitions() {
+        return $this->entityFieldManager->clearCachedFieldDefinitions();
+      }
+
+      public function useCaches($use_caches = FALSE) {
+        return $this->entityFieldManager->useCaches($use_caches);
+      }
+
+      public function getExtraFields($entity_type_id, $bundle) {
+        return $this->entityFieldManager->getExtraFields($entity_type_id, $bundle);
+      }
+
+    };
+
+    $this->container->set('entity_field.manager', $decorated_entity_field_manager);
+    $this->createEntityStub('migrate_string_id_entity_test');

could we simplify this if the anonymous class extended from the existing entity field manager?

niklan’s picture

StatusFileSize
new3.07 KB
new4.15 KB
new3.57 KB

Updated patch according to suggestion from #12.

The last submitted patch, 13: core-3335269-13-test-only.patch, failed testing. View results

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Thank you, looks ready again

niklan’s picture

Btw, should such tests be marked somehow? Because when an interface will start using typehints this test becomes pointless, it will be covered by PHP directly.

larowlan’s picture

+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php	(date 1680103443219)
@@ -330,7 +330,7 @@
+    $bundle_key = $this->getKey('bundle') ?: $this->storage->getEntityTypeId();

I don't think this is the correct fix, a 'bundle key' is the name of the row property that contains the bundle field.

I think instead we should change this line

 $fields = $this->entityFieldManager
      ->getFieldDefinitions($this->storage->getEntityTypeId(), $row->getDestinationProperty($bundle_key));

which is a few rows lower.

I think it should be

 $fields = $this->entityFieldManager
      ->getFieldDefinitions($this->storage->getEntityTypeId(), $row->getDestinationProperty($bundle_key) ?? $this->storage->getEntityTypeId());

Sorry for not picking that up in my earlier review.

Fine to self RTBC after making that change.

I was tempted to just make that change on commit but felt it was best to go through the peer review process.

Once again, sorry for not picking that up the first time.

Btw, should such tests be marked somehow? Because when an interface will start using typehints this test becomes pointless, it will be covered by PHP directly.

Ordinarily we'd add a followup for that, i.e a new issue 'remove test x once [something] is complete' and put an // @todo remove this class in [url to followup] that way when someone resolves the issue they can search by URL for todos.

I'd support doing that here if you're rerolling for the above, as I agree, a proper type hint would have prevented this.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
niklan’s picture

Status: Needs work » Needs review
StatusFileSize
new4.46 KB
new3.28 KB
new2.24 KB

Updated patch to reflect requested changes in #17. Also, please check my language for @todo. I've also added void return typhing for test method. While I'm searching for #3050720: [Meta] Implement strict typing in existing code I've also found that all new code should use type-hints. Since this is a test method, it shouldn't affect any project.

niklan’s picture

StatusFileSize
new4.6 KB
new3.28 KB
new2.39 KB
new1.05 KB

I made a mistake, just straightforward passes entity type ID as a bundle. Fixed in this patch. Ignore #19. For easier code understanding, I've introduced a new $bundle variable with the logic for bundle, instead of doing this as an argument in a method call.

P.s. also found a typo in @todo. But won't update it for now to avoid a lot of notifications. I want to hear how to better write it anyway, then I update it properly with a typo fix.

The last submitted patch, 20: core-3335269-20.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 20: core-3335269-20-test-only.patch, failed testing. View results

andypost’s picture

Status: Needs work » Reviewed & tested by the community

Retested and #17 addressed

  • larowlan committed 2b801817 on 10.0.x
    Issue #3335269 by Niklan: Entity stubs doesn't follows fallback logic...

  • larowlan committed 6d4a6d6a on 10.1.x
    Issue #3335269 by Niklan: Entity stubs doesn't follows fallback logic...
larowlan’s picture

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

Pushed to 10.1.x and 10.0.x

Queued a 9.5.x test and will backport there too if it passes

larowlan’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

The patch doesn't apply to 9.5.x, so changing to patch (to be ported)

niklan’s picture

StatusFileSize
new4.66 KB
catch’s picture

Status: Patch (to be ported) » Fixed

Committed/pushed to 9.5.x, thanks!

  • catch committed 51309354 on 9.5.x
    Issue #3335269 by Niklan, larowlan: Entity stubs doesn't follows...

Status: Fixed » Closed (fixed)

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