Skip to content
Snippets Groups Projects
Verified Commit f342c259 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3257457 by DieterHolvoet, scott_euser, smustgrave, ankithashetty,...

Issue #3257457 by DieterHolvoet, scott_euser, smustgrave, ankithashetty, alexpott, larowlan, Berdir, dww: AmbiguousBundleClassException if multiple entity types share the same class

(cherry picked from commit 644d8f2a)
parent 5786e6e8
No related branches found
No related tags found
23 merge requests!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9944Issue #3483353: Consider making the createCopy config action optionally fail...,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8597Draft: Issue #3442259 by catch, quietone, dww: Reduce time of Migrate Upgrade tests...,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!8325Update file Sort.php,!8095Expose document root on install,!7930Resolve #3427374 "Taxonomytid viewsargumentdefault plugin",!7627Issue #3439440 by nicxvan, Binoli Lalani, longwave: Remove country support from DateFormatter,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!7384Add constraints to system.advisories,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #138421 passed
Pipeline: drupal

#138424

    ...@@ -708,7 +708,7 @@ services: ...@@ -708,7 +708,7 @@ services:
    Drupal\Core\Entity\EntityTypeManagerInterface: '@entity_type.manager' Drupal\Core\Entity\EntityTypeManagerInterface: '@entity_type.manager'
    entity_type.repository: entity_type.repository:
    class: Drupal\Core\Entity\EntityTypeRepository class: Drupal\Core\Entity\EntityTypeRepository
    arguments: ['@entity_type.manager'] arguments: ['@entity_type.manager', '@entity_type.bundle.info']
    Drupal\Core\Entity\EntityTypeRepositoryInterface: '@entity_type.repository' Drupal\Core\Entity\EntityTypeRepositoryInterface: '@entity_type.repository'
    entity_type.bundle.info: entity_type.bundle.info:
    class: Drupal\Core\Entity\EntityTypeBundleInfo class: Drupal\Core\Entity\EntityTypeBundleInfo
    ......
    ...@@ -30,14 +30,12 @@ class EntityTypeRepository implements EntityTypeRepositoryInterface { ...@@ -30,14 +30,12 @@ class EntityTypeRepository implements EntityTypeRepositoryInterface {
    */ */
    protected $classNameEntityTypeMap = []; protected $classNameEntityTypeMap = [];
    /** public function __construct(EntityTypeManagerInterface $entity_type_manager, protected ?EntityTypeBundleInfoInterface $entityTypeBundleInfo = NULL) {
    * Constructs a new EntityTypeRepository.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    * The entity type manager.
    */
    public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager; $this->entityTypeManager = $entity_type_manager;
    if (!isset($this->entityTypeBundleInfo)) {
    @trigger_error('Calling EntityTypeRepository::__construct() without the $entityTypeBundleInfo argument is deprecated in drupal:10.3.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/3365164', E_USER_DEPRECATED);
    $this->entityTypeBundleInfo = \Drupal::service('entity_type.bundle.info');
    }
    } }
    /** /**
    ...@@ -83,7 +81,7 @@ public function getEntityTypeFromClass($class_name) { ...@@ -83,7 +81,7 @@ public function getEntityTypeFromClass($class_name) {
    $entity_type_id = NULL; $entity_type_id = NULL;
    $definitions = $this->entityTypeManager->getDefinitions(); $definitions = $this->entityTypeManager->getDefinitions();
    foreach ($definitions as $entity_type) { foreach ($definitions as $entity_type) {
    if ($entity_type->getOriginalClass() == $class_name || $entity_type->getClass() == $class_name) { if ($entity_type->getOriginalClass() == $class_name || $entity_type->getClass() == $class_name) {
    $entity_type_id = $entity_type->id(); $entity_type_id = $entity_type->id();
    if ($same_class++) { if ($same_class++) {
    throw new AmbiguousEntityClassException($class_name); throw new AmbiguousEntityClassException($class_name);
    ...@@ -95,11 +93,14 @@ public function getEntityTypeFromClass($class_name) { ...@@ -95,11 +93,14 @@ public function getEntityTypeFromClass($class_name) {
    // a separate loop to avoid false positives, since an entity class can // a separate loop to avoid false positives, since an entity class can
    // subclass another entity class. // subclass another entity class.
    if (!$entity_type_id) { if (!$entity_type_id) {
    foreach ($definitions as $entity_type) { $bundle_info = $this->entityTypeBundleInfo->getAllBundleInfo();
    if (is_subclass_of($class_name, $entity_type->getOriginalClass()) || is_subclass_of($class_name, $entity_type->getClass())) { foreach ($bundle_info as $info_entity_type_id => $bundles) {
    $entity_type_id = $entity_type->id(); foreach ($bundles as $info) {
    if ($same_class++) { if (isset($info['class']) && $info['class'] === $class_name) {
    throw new AmbiguousBundleClassException($class_name); $entity_type_id = $info_entity_type_id;
    if ($same_class++) {
    throw new AmbiguousBundleClassException($class_name);
    }
    } }
    } }
    } }
    ......
    ...@@ -5,11 +5,14 @@ ...@@ -5,11 +5,14 @@
    * Support module for testing entity bundle classes. * Support module for testing entity bundle classes.
    */ */
    use Drupal\entity_test\Entity\EntityTest;
    use Drupal\entity_test_bundle_class\Entity\EntityTestAmbiguousBundleClass; use Drupal\entity_test_bundle_class\Entity\EntityTestAmbiguousBundleClass;
    use Drupal\entity_test_bundle_class\Entity\EntityTestBundleClass; use Drupal\entity_test_bundle_class\Entity\EntityTestBundleClass;
    use Drupal\entity_test_bundle_class\Entity\EntityTestUserClass; use Drupal\entity_test_bundle_class\Entity\EntityTestUserClass;
    use Drupal\entity_test_bundle_class\Entity\EntityTestVariant; use Drupal\entity_test_bundle_class\Entity\EntityTestVariant;
    use Drupal\entity_test_bundle_class\Entity\NonInheritingBundleClass; use Drupal\entity_test_bundle_class\Entity\NonInheritingBundleClass;
    use Drupal\entity_test_bundle_class\Entity\SharedEntityTestBundleClassA;
    use Drupal\entity_test_bundle_class\Entity\SharedEntityTestBundleClassB;
    /** /**
    * Implements hook_entity_bundle_info_alter(). * Implements hook_entity_bundle_info_alter().
    ...@@ -36,6 +39,16 @@ function entity_test_bundle_class_entity_bundle_info_alter(&$bundles) { ...@@ -36,6 +39,16 @@ function entity_test_bundle_class_entity_bundle_info_alter(&$bundles) {
    if (\Drupal::state()->get('entity_test_bundle_class_does_not_exist', FALSE)) { if (\Drupal::state()->get('entity_test_bundle_class_does_not_exist', FALSE)) {
    $bundles['entity_test']['bundle_class']['class'] = '\Drupal\Core\NonExistentClass'; $bundles['entity_test']['bundle_class']['class'] = '\Drupal\Core\NonExistentClass';
    } }
    // Have two bundles share the same base entity class.
    $bundles['shared_type']['bundle_a'] = [
    'label' => 'Bundle A',
    'class' => SharedEntityTestBundleClassA::class,
    ];
    $bundles['shared_type']['bundle_b'] = [
    'label' => 'Bundle B',
    'class' => SharedEntityTestBundleClassB::class,
    ];
    } }
    /** /**
    ...@@ -47,3 +60,18 @@ function entity_test_bundle_class_entity_type_alter(&$entity_types) { ...@@ -47,3 +60,18 @@ function entity_test_bundle_class_entity_type_alter(&$entity_types) {
    $entity_types['entity_test']->setClass(EntityTestVariant::class); $entity_types['entity_test']->setClass(EntityTestVariant::class);
    } }
    } }
    /**
    * Implements hook_entity_type_build().
    */
    function entity_test_bundle_class_entity_type_build(array &$entity_types): void {
    // Have multiple entity types share the same class as Entity Test.
    // This allows us to test that AmbiguousBundleClassException does not
    // get thrown when sharing classes.
    /** @var \Drupal\Core\Entity\ContentEntityType $original_type */
    $cloned_type = clone $entity_types['entity_test'];
    $cloned_type->set('bundle_of', 'entity_test');
    $entity_types['shared_type'] = $cloned_type;
    $entity_types['shared_type']->setClass(EntityTest::class);
    }
    <?php
    namespace Drupal\entity_test_bundle_class\Entity;
    use Drupal\entity_test\Entity\EntityTest;
    /**
    * A bundle class that shares the same entity type as entity_test.
    */
    class SharedEntityTestBundleClassA extends EntityTest {
    }
    <?php
    namespace Drupal\entity_test_bundle_class\Entity;
    use Drupal\entity_test\Entity\EntityTest;
    /**
    * A bundle class that shares the same entity type as entity_test.
    */
    class SharedEntityTestBundleClassB extends EntityTest {
    }
    ...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
    use Drupal\entity_test_bundle_class\Entity\EntityTestBundleClass; use Drupal\entity_test_bundle_class\Entity\EntityTestBundleClass;
    use Drupal\entity_test_bundle_class\Entity\EntityTestUserClass; use Drupal\entity_test_bundle_class\Entity\EntityTestUserClass;
    use Drupal\entity_test_bundle_class\Entity\EntityTestVariant; use Drupal\entity_test_bundle_class\Entity\EntityTestVariant;
    use Drupal\entity_test_bundle_class\Entity\SharedEntityTestBundleClassA;
    use Drupal\entity_test_bundle_class\Entity\SharedEntityTestBundleClassB;
    use Drupal\user\Entity\User; use Drupal\user\Entity\User;
    /** /**
    ...@@ -56,6 +58,10 @@ public function testEntitySubclass() { ...@@ -56,6 +58,10 @@ public function testEntitySubclass() {
    $entity = EntityTestBundleClass::create(); $entity = EntityTestBundleClass::create();
    $this->assertInstanceOf(EntityTestBundleClass::class, $entity); $this->assertInstanceOf(EntityTestBundleClass::class, $entity);
    // Verify that bundle returns bundle_class when create is called without
    // passing a bundle.
    $this->assertSame($entity->bundle(), 'bundle_class');
    // Check that both preCreate() and postCreate() were called once. // Check that both preCreate() and postCreate() were called once.
    $this->assertEquals(1, EntityTestBundleClass::$preCreateCount); $this->assertEquals(1, EntityTestBundleClass::$preCreateCount);
    $this->assertEquals(1, $entity->postCreateCount); $this->assertEquals(1, $entity->postCreateCount);
    ...@@ -239,6 +245,18 @@ public function testAmbiguousBundleClassExceptionEntityTypeRepository() { ...@@ -239,6 +245,18 @@ public function testAmbiguousBundleClassExceptionEntityTypeRepository() {
    $entity_type = $this->container->get('entity_type.repository')->getEntityTypeFromClass(EntityTestAmbiguousBundleClass::class); $entity_type = $this->container->get('entity_type.repository')->getEntityTypeFromClass(EntityTestAmbiguousBundleClass::class);
    } }
    /**
    * Checks that no exception is thrown when two bundles share an entity class.
    *
    * @covers Drupal\Core\Entity\EntityTypeRepository::getEntityTypeFromClass
    */
    public function testNoAmbiguousBundleClassExceptionSharingEntityClass(): void {
    $shared_type_a = $this->container->get('entity_type.repository')->getEntityTypeFromClass(SharedEntityTestBundleClassA::class);
    $shared_type_b = $this->container->get('entity_type.repository')->getEntityTypeFromClass(SharedEntityTestBundleClassB::class);
    $this->assertSame('shared_type', $shared_type_a);
    $this->assertSame('shared_type', $shared_type_b);
    }
    /** /**
    * Checks exception thrown if a bundle class doesn't extend the entity class. * Checks exception thrown if a bundle class doesn't extend the entity class.
    */ */
    ......
    ...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
    namespace Drupal\Tests\Core\Entity; namespace Drupal\Tests\Core\Entity;
    use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Plugin\Exception\PluginNotFoundException;
    use Drupal\Core\Entity\EntityBase;
    use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
    use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
    use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
    use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
    use Drupal\Core\Entity\EntityTypeRepository; use Drupal\Core\Entity\EntityTypeRepository;
    ...@@ -34,6 +36,13 @@ class EntityTypeRepositoryTest extends UnitTestCase { ...@@ -34,6 +36,13 @@ class EntityTypeRepositoryTest extends UnitTestCase {
    */ */
    protected $entityTypeManager; protected $entityTypeManager;
    /**
    * The entity type bundle info service.
    *
    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
    */
    protected $entityTypeBundleInfo;
    /** /**
    * {@inheritdoc} * {@inheritdoc}
    */ */
    ...@@ -41,8 +50,9 @@ protected function setUp(): void { ...@@ -41,8 +50,9 @@ protected function setUp(): void {
    parent::setUp(); parent::setUp();
    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class); $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
    $this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
    $this->entityTypeRepository = new EntityTypeRepository($this->entityTypeManager->reveal()); $this->entityTypeRepository = new EntityTypeRepository($this->entityTypeManager->reveal(), $this->entityTypeBundleInfo->reveal());
    } }
    /** /**
    ...@@ -79,6 +89,7 @@ protected function setUpEntityTypeDefinitions($definitions = []) { ...@@ -79,6 +89,7 @@ protected function setUpEntityTypeDefinitions($definitions = []) {
    } }
    }); });
    $this->entityTypeManager->getDefinitions()->willReturn($definitions); $this->entityTypeManager->getDefinitions()->willReturn($definitions);
    $this->entityTypeBundleInfo->getAllBundleInfo()->willReturn([]);
    } }
    /** /**
    ...@@ -175,4 +186,47 @@ public function testGetEntityTypeFromClassAmbiguous() { ...@@ -175,4 +186,47 @@ public function testGetEntityTypeFromClassAmbiguous() {
    $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\apple\Entity\Apple'); $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\apple\Entity\Apple');
    } }
    /**
    * @covers ::getEntityTypeFromClass
    */
    public function testGetEntityTypeFromClassAmbiguousBundleClass(): void {
    $blackcurrant = $this->prophesize(EntityTypeInterface::class);
    $blackcurrant->getOriginalClass()->willReturn(Apple::class);
    $blackcurrant->getClass()->willReturn(Blackcurrant::class);
    $blackcurrant->id()->willReturn('blackcurrant');
    $gala = $this->prophesize(EntityTypeInterface::class);
    $gala->getOriginalClass()->willReturn(Apple::class);
    $gala->getClass()->willReturn(RoyalGala::class);
    $gala->id()->willReturn('gala');
    $this->setUpEntityTypeDefinitions([
    'blackcurrant' => $blackcurrant,
    'gala' => $gala,
    ]);
    $this->entityTypeBundleInfo->getAllBundleInfo()->willReturn([
    'gala' => [
    'royal_gala' => [
    'label' => 'Royal Gala',
    'class' => RoyalGala::class,
    ],
    ],
    ]);
    $this->assertSame('gala', $this->entityTypeRepository->getEntityTypeFromClass(RoyalGala::class));
    }
    }
    class Fruit extends EntityBase {
    }
    class Apple extends Fruit {
    }
    class RoyalGala extends Apple {
    }
    class Blackcurrant extends Fruit {
    } }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment