Unverified Commit 61bff4ef authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #2870874 by clayfreeman, tim.plunkett, Wim Leers, larowlan, mradcliffe:...

Issue #2870874 by clayfreeman, tim.plunkett, Wim Leers, larowlan, mradcliffe: EntityBase::getTypedData() does not return the correct data type for configuration entities due to lack of consideration for data type derivatives
parent b8004807
Loading
Loading
Loading
Loading
+27 −1
Original line number Diff line number Diff line
@@ -587,12 +587,38 @@ public function toArray() {
   */
  public function getTypedData() {
    if (!isset($this->typedData)) {
      $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
      $class = $this->getTypedDataClass();
      $this->typedData = $class::createFromEntity($this);
    }
    return $this->typedData;
  }

  /**
   * Returns the typed data class name for this entity.
   *
   * @return string
   *   The string representing the typed data class name.
   *
   * @see \Drupal\Core\Entity\Plugin\DataType\EntityAdapter
   */
  private function getTypedDataClass(): string {
    $typed_data_manager = \Drupal::typedDataManager();

    // Check more specific data types that could apply to this entity.
    $candidate_data_types = [
      "entity:{$this->getEntityTypeId()}:{$this->bundle()}",
      "entity:{$this->getEntityTypeId()}",
    ];
    foreach ($candidate_data_types as $candidate_data_type) {
      if ($typed_data_manager->hasDefinition($candidate_data_type)) {
        return $typed_data_manager->getDefinition($candidate_data_type)['class'];
      }
    }

    // Fall back to the generic entity definition.
    return $typed_data_manager->getDefinition('entity')['class'];
  }

  /**
   * {@inheritdoc}
   */
+50 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\KernelTests\Core\Entity;

use Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;

use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\entity_test\Entity\EntityTestWithBundle;

/**
 * Tests the functionality provided by \Drupal\Core\Entity\EntityBase.
 *
 * @coversDefaultClass \Drupal\Core\Entity\EntityBase
 * @group Entity
 */
class EntityBaseTest extends EntityKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->installEntitySchema('entity_test_with_bundle');
  }

  /**
   * Tests that the correct entity adapter is returned.
   *
   * @covers ::getTypedData
   * @covers ::getClass
   */
  public function testGetTypedData() {
    $bundle = EntityTestBundle::create([
      'id' => $this->randomMachineName(),
    ]);
    $bundle->save();

    $entity = EntityTestWithBundle::create([
      'type' => $bundle->id(),
      'name' => $this->randomString(),
    ]);
    $entity->save();

    $this->assertInstanceOf(ConfigEntityAdapter::class, $bundle->getTypedData());
    $this->assertInstanceOf(EntityAdapter::class, $entity->getTypedData());
  }

}
+80 −1
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
@@ -157,7 +158,6 @@ protected function setUp(): void {
    $this->typedDataManager = $this->createMock(TypedDataManagerInterface::class);
    $this->typedDataManager->expects($this->any())
      ->method('getDefinition')
      ->with('entity')
      ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\EntityAdapter']));

    $english = new Language(['id' => 'en']);
@@ -333,6 +333,85 @@ public function testPreSaveRevision() {
    $this->assertNull($this->entity->preSaveRevision($storage, $record));
  }

  /**
   * Data provider for the ::getTypedData() test.
   *
   * The following entity data definitions, the first two being derivatives of
   * the last definition, will be tested in order:
   *
   * 1. entity:$entity_type:$bundle
   * 2. entity:$entity_type
   * 3. entity
   *
   * @see \Drupal\Core\Entity\EntityBase::getTypedData()
   * @see \Drupal\Core\Entity\EntityBase::getTypedDataClass()
   * @see \Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver
   *
   * @return array
   *   Array of arrays with the following elements:
   *   - A bool whether to provide a bundle-specific definition.
   *   - A bool whether to provide an entity type-specific definition.
   */
  public function providerTestTypedData(): array {
    return [
      'Entity data definition derivative with entity type and bundle' => [
        TRUE,
        TRUE,
      ],
      'Entity data definition derivative with entity type' => [
        FALSE,
        TRUE,
      ],
      'Entity data definition' => [
        FALSE,
        FALSE,
      ],
    ];
  }

  /**
   * Tests each condition in EntityBase::getTypedData().
   *
   * @covers ::getTypedData
   * @dataProvider providerTestTypedData
   */
  public function testTypedData(bool $bundle_typed_data_definition, bool $entity_type_typed_data_definition): void {
    $expected = EntityAdapter::class;

    $typedDataManager = $this->createMock(TypedDataManagerInterface::class);
    $typedDataManager->expects($this->once())
      ->method('getDefinition')
      ->willReturnMap([
        [
          "entity:{$this->entityTypeId}:{$this->bundle}", FALSE,
          $bundle_typed_data_definition ? ['class' => $expected] : NULL,
        ],
        [
          "entity:{$this->entityTypeId}", FALSE,
          $entity_type_typed_data_definition ? ['class' => $expected] : NULL,
        ],
        [
          'entity', TRUE,
          ['class' => $expected],
        ],
      ]);

    // Temporarily replace the appropriate services in the container.
    $container = \Drupal::getContainer();
    $container->set('typed_data_manager', $typedDataManager);
    \Drupal::setContainer($container);

    // Create a mock entity used to retrieve typed data.
    $entity = $this->getMockForAbstractClass(ContentEntityBase::class, [
      [],
      $this->entityTypeId,
      $this->bundle,
    ], '', TRUE, TRUE, TRUE, ['isNew']);

    // Assert that the returned data type is an instance of EntityAdapter.
    $this->assertInstanceOf($expected, $entity->getTypedData());
  }

  /**
   * @covers ::validate
   */