From f7d8fe6f1db5c04ee8ca7a80f9e7ef8500cfe5c1 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Wed, 28 Sep 2022 10:39:07 +0100
Subject: [PATCH] Issue #3266287 by lind101, alexpott, joachim, larowlan,
 quietone: Make buildBundleFieldDefinitions use Bundle Classes for building
 bundleFieldDefinitions

---
 .../Drupal/Core/Entity/EntityFieldManager.php |  4 +-
 .../Core/Entity/EntityFieldManagerTest.php    | 90 ++++++++++++++++++-
 2 files changed, 91 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityFieldManager.php b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
index 8ccdb86a0ab9..1e7052a47de0 100644
--- a/core/lib/Drupal/Core/Entity/EntityFieldManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
@@ -373,7 +373,9 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
    */
   protected function buildBundleFieldDefinitions($entity_type_id, $bundle, array $base_field_definitions) {
     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-    $class = $entity_type->getClass();
+
+    // Use a bundle specific class if one is defined.
+    $class = $this->entityTypeManager->getStorage($entity_type_id)->getEntityClass($bundle);
 
     // Allow the entity class to provide bundle fields and bundle-specific
     // overrides of base fields.
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php
index df404f8e7fcb..bfcd5ef67b24 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php
@@ -11,13 +11,13 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
-use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 use Drupal\Core\Entity\EntityFieldManager;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -247,8 +247,54 @@ public function testGetBaseFieldDefinitions() {
   public function testGetFieldDefinitions() {
     $field_definition = $this->setUpEntityWithFieldDefinition();
 
+    $bundle_field_definition = $this->prophesize()
+      ->willImplement(FieldDefinitionInterface::class)
+      ->willImplement(FieldStorageDefinitionInterface::class);
+
+    // Define bundle fields to be stored on the default Entity class.
+    $bundle_fields = [
+      'the_entity_id' => [
+        'test_entity_bundle' => [
+          'id_bundle' => $bundle_field_definition->reveal(),
+        ],
+        'test_entity_bundle_class' => [
+          'some_extra_field' => $bundle_field_definition->reveal(),
+        ],
+      ],
+    ];
+
+    // Define bundle fields to be stored on the bundle class.
+    $bundle_class_fields = [
+      'the_entity_id' => [
+        'test_entity_bundle_class' => [
+          'id_bundle_class' => $bundle_field_definition->reveal(),
+        ],
+      ],
+    ];
+
+    EntityTypeManagerTestEntity::$bundleFieldDefinitions = $bundle_fields;
+    EntityTypeManagerTestEntityBundle::$bundleClassFieldDefinitions = $bundle_class_fields;
+
+    // Test that only base fields are retrieved.
     $expected = ['id' => $field_definition];
+    $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'some_other_bundle'));
+
+    // Test that base fields and bundle fields from the default entity class are
+    // retrieved.
+    $expected = [
+      'id' => $field_definition,
+      'id_bundle' => $bundle_fields['the_entity_id']['test_entity_bundle']['id_bundle'],
+    ];
     $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_entity_bundle'));
+
+    // Test that base fields and bundle fields from the bundle class and
+    // entity class are retrieved
+    $expected = [
+      'id' => $field_definition,
+      'some_extra_field' => $bundle_fields['the_entity_id']['test_entity_bundle_class']['some_extra_field'],
+      'id_bundle_class' => $bundle_class_fields['the_entity_id']['test_entity_bundle_class']['id_bundle_class'],
+    ];
+    $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_entity_bundle_class'));
   }
 
   /**
@@ -575,8 +621,18 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f
     $this->entityType = $this->prophesize(EntityTypeInterface::class);
     $this->setUpEntityTypeDefinitions(['test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type]);
 
-    $storage = $this->prophesize(ConfigEntityStorageInterface::class);
+    $storage = $this->prophesize(EntityStorageInterface::class);
     $storage->loadMultiple(Argument::type('array'))->willReturn([]);
+
+    // By default, make the storage entity class lookup return the
+    // EntityTypeManagerTestEntity class
+    $storage->getEntityClass(NULL)->willReturn(EntityTypeManagerTestEntity::class);
+    $storage->getEntityClass(Argument::type('string'))->willReturn(EntityTypeManagerTestEntity::class);
+    // When using the "test_entity_bundle_class" bundle, return the
+    // EntityTypeManagerTestEntityBundle class
+    $storage->getEntityClass('test_entity_bundle_class')->willReturn(EntityTypeManagerTestEntityBundle::class);
+
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
     $this->entityTypeManager->getStorage('base_field_override')->willReturn($storage->reveal());
 
     $this->entityType->getClass()->willReturn($entity_class);
@@ -864,3 +920,33 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
   }
 
 }
+
+/**
+ * Provides a bundle specific class with dummy static method implementations.
+ */
+abstract class EntityTypeManagerTestEntityBundle extends EntityTypeManagerTestEntity {
+
+  /**
+   * The bundle class field definitions.
+   *
+   * @var array[]
+   *   Keys are entity type IDs, values are arrays of which the keys are bundle
+   *   names and the values are field definitions.
+   */
+  public static $bundleClassFieldDefinitions = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
+    $definitions = parent::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
+
+    if (isset(static::$bundleClassFieldDefinitions[$entity_type->id()][$bundle])) {
+      $definitions += static::$bundleClassFieldDefinitions[$entity_type->id()][$bundle];
+    }
+
+    return $definitions;
+
+  }
+
+}
-- 
GitLab