You are here

EntityTypeManagerTest.php in Drupal 9

File

core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
View source
<?php

/**
 * @file
 * Contains \Drupal\Tests\Core\Entity\EntityTypeManagerTest.
 */
namespace Drupal\Tests\Core\Entity;

use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityHandlerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Exception\InvalidLinkTemplateException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @coversDefaultClass \Drupal\Core\Entity\EntityTypeManager
 * @group Entity
 */
class EntityTypeManagerTest extends UnitTestCase {

  /**
   * The entity type manager under test.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */
  protected $entityTypeManager;

  /**
   * The translation manager.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $translationManager;

  /**
   * The plugin discovery.
   *
   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $discovery;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $moduleHandler;

  /**
   * The cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $cacheBackend;

  /**
   * The entity last installed schema repository.
   *
   * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityLastInstalledSchemaRepository;

  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $this->moduleHandler = $this
      ->prophesize(ModuleHandlerInterface::class);
    $this->moduleHandler
      ->getImplementations('entity_type_build')
      ->willReturn([]);
    $this->moduleHandler
      ->alter('entity_type', Argument::type('array'))
      ->willReturn(NULL);
    $this->cacheBackend = $this
      ->prophesize(CacheBackendInterface::class);
    $this->translationManager = $this
      ->prophesize(TranslationInterface::class);
    $this->entityLastInstalledSchemaRepository = $this
      ->prophesize(EntityLastInstalledSchemaRepositoryInterface::class);
    $this->entityTypeManager = new TestEntityTypeManager(new \ArrayObject(), $this->moduleHandler
      ->reveal(), $this->cacheBackend
      ->reveal(), $this->translationManager
      ->reveal(), $this
      ->getClassResolverStub(), $this->entityLastInstalledSchemaRepository
      ->reveal());
    $this->discovery = $this
      ->prophesize(DiscoveryInterface::class);
    $this->entityTypeManager
      ->setDiscovery($this->discovery
      ->reveal());
  }

  /**
   * Sets up the entity type manager to be tested.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
   *   (optional) An array of entity type definitions.
   */
  protected function setUpEntityTypeDefinitions($definitions = []) {
    $class = $this
      ->getMockClass(EntityInterface::class);
    foreach ($definitions as $key => $entity_type) {

      // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
      // by \Drupal\Core\Entity\EntityTypeManager::processDefinition() so it must
      // always be mocked.
      $entity_type
        ->getLinkTemplates()
        ->willReturn([]);

      // Give the entity type a legitimate class to return.
      $entity_type
        ->getClass()
        ->willReturn($class);
      $entity_type
        ->setClass($class)
        ->willReturn($entity_type
        ->reveal());
      $definitions[$key] = $entity_type
        ->reveal();
    }
    $this->discovery
      ->getDefinition(Argument::cetera())
      ->will(function ($args) use ($definitions) {
      $entity_type_id = $args[0];
      $exception_on_invalid = $args[1];
      if (isset($definitions[$entity_type_id])) {
        return $definitions[$entity_type_id];
      }
      elseif (!$exception_on_invalid) {
        return NULL;
      }
      else {
        throw new PluginNotFoundException($entity_type_id);
      }
    });
    $this->discovery
      ->getDefinitions()
      ->willReturn($definitions);
  }

  /**
   * Tests the hasHandler() method.
   *
   * @covers ::hasHandler
   *
   * @dataProvider providerTestHasHandler
   */
  public function testHasHandler($entity_type_id, $expected) {
    $apple = $this
      ->prophesize(EntityTypeInterface::class);
    $apple
      ->hasHandlerClass('storage')
      ->willReturn(TRUE);
    $banana = $this
      ->prophesize(EntityTypeInterface::class);
    $banana
      ->hasHandlerClass('storage')
      ->willReturn(FALSE);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $apple,
      'banana' => $banana,
    ]);
    $entity_type = $this->entityTypeManager
      ->hasHandler($entity_type_id, 'storage');
    $this
      ->assertSame($expected, $entity_type);
  }

  /**
   * Provides test data for testHasHandler().
   *
   * @return array
   *   Test data.
   */
  public function providerTestHasHandler() {
    return [
      [
        'apple',
        TRUE,
      ],
      [
        'banana',
        FALSE,
      ],
      [
        'pear',
        FALSE,
      ],
    ];
  }

  /**
   * Tests the getStorage() method.
   *
   * @covers ::getStorage
   */
  public function testGetStorage() {
    $class = $this
      ->getTestHandlerClass();
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getHandlerClass('storage')
      ->willReturn($class);
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->assertInstanceOf($class, $this->entityTypeManager
      ->getStorage('test_entity_type'));
  }

  /**
   * Tests the getListBuilder() method.
   *
   * @covers ::getListBuilder
   */
  public function testGetListBuilder() {
    $class = $this
      ->getTestHandlerClass();
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getHandlerClass('list_builder')
      ->willReturn($class);
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->assertInstanceOf($class, $this->entityTypeManager
      ->getListBuilder('test_entity_type'));
  }

  /**
   * Tests the getViewBuilder() method.
   *
   * @covers ::getViewBuilder
   */
  public function testGetViewBuilder() {
    $class = $this
      ->getTestHandlerClass();
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getHandlerClass('view_builder')
      ->willReturn($class);
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->assertInstanceOf($class, $this->entityTypeManager
      ->getViewBuilder('test_entity_type'));
  }

  /**
   * Tests the getAccessControlHandler() method.
   *
   * @covers ::getAccessControlHandler
   */
  public function testGetAccessControlHandler() {
    $class = $this
      ->getTestHandlerClass();
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getHandlerClass('access')
      ->willReturn($class);
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->assertInstanceOf($class, $this->entityTypeManager
      ->getAccessControlHandler('test_entity_type'));
  }

  /**
   * Tests the getFormObject() method.
   *
   * @covers ::getFormObject
   */
  public function testGetFormObject() {
    $apple = $this
      ->prophesize(EntityTypeInterface::class);
    $apple
      ->getFormClass('default')
      ->willReturn(TestEntityForm::class);
    $banana = $this
      ->prophesize(EntityTypeInterface::class);
    $banana
      ->getFormClass('default')
      ->willReturn(TestEntityFormInjected::class);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $apple,
      'banana' => $banana,
    ]);
    $apple_form = $this->entityTypeManager
      ->getFormObject('apple', 'default');
    $this
      ->assertInstanceOf(TestEntityForm::class, $apple_form);
    $this
      ->assertInstanceOf(ModuleHandlerInterface::class, $apple_form->moduleHandler);
    $this
      ->assertInstanceOf(TranslationInterface::class, $apple_form->stringTranslation);
    $banana_form = $this->entityTypeManager
      ->getFormObject('banana', 'default');
    $this
      ->assertInstanceOf(TestEntityFormInjected::class, $banana_form);
    $this
      ->assertEquals('yellow', $banana_form->color);
  }

  /**
   * Tests the getFormObject() method with an invalid operation.
   *
   * @covers ::getFormObject
   */
  public function testGetFormObjectInvalidOperation() {
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getFormClass('edit')
      ->willReturn('');
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->expectException(InvalidPluginDefinitionException::class);
    $this->entityTypeManager
      ->getFormObject('test_entity_type', 'edit');
  }

  /**
   * Tests the getHandler() method.
   *
   * @covers ::getHandler
   */
  public function testGetHandler() {
    $class = get_class($this
      ->getMockForAbstractClass(TestEntityHandlerBase::class));
    $apple = $this
      ->prophesize(EntityTypeInterface::class);
    $apple
      ->getHandlerClass('storage')
      ->willReturn($class);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $apple,
    ]);
    $apple_controller = $this->entityTypeManager
      ->getHandler('apple', 'storage');
    $this
      ->assertInstanceOf($class, $apple_controller);
    $this
      ->assertInstanceOf(ModuleHandlerInterface::class, $apple_controller->moduleHandler);
    $this
      ->assertInstanceOf(TranslationInterface::class, $apple_controller->stringTranslation);
  }

  /**
   * Tests the getHandler() method when no controller is defined.
   *
   * @covers ::getHandler
   */
  public function testGetHandlerMissingHandler() {
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $entity
      ->getHandlerClass('storage')
      ->willReturn('');
    $this
      ->setUpEntityTypeDefinitions([
      'test_entity_type' => $entity,
    ]);
    $this
      ->expectException(InvalidPluginDefinitionException::class);
    $this->entityTypeManager
      ->getHandler('test_entity_type', 'storage');
  }

  /**
   * @covers ::getRouteProviders
   */
  public function testGetRouteProviders() {
    $apple = $this
      ->prophesize(EntityTypeInterface::class);
    $apple
      ->getRouteProviderClasses()
      ->willReturn([
      'default' => TestRouteProvider::class,
    ]);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $apple,
    ]);
    $apple_route_provider = $this->entityTypeManager
      ->getRouteProviders('apple');
    $this
      ->assertInstanceOf(TestRouteProvider::class, $apple_route_provider['default']);
    $this
      ->assertInstanceOf(ModuleHandlerInterface::class, $apple_route_provider['default']->moduleHandler);
    $this
      ->assertInstanceOf(TranslationInterface::class, $apple_route_provider['default']->stringTranslation);
  }

  /**
   * Tests the processDefinition() method.
   *
   * @covers ::processDefinition
   */
  public function testProcessDefinition() {
    $apple = $this
      ->prophesize(EntityTypeInterface::class);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $apple,
    ]);
    $apple
      ->getLinkTemplates()
      ->willReturn([
      'canonical' => 'path/to/apple',
    ]);
    $definition = $apple
      ->reveal();
    $this
      ->expectException(InvalidLinkTemplateException::class);
    $this
      ->expectExceptionMessage("Link template 'canonical' for entity type 'apple' must start with a leading slash, the current link template is 'path/to/apple'");
    $this->entityTypeManager
      ->processDefinition($definition, 'apple');
  }

  /**
   * Tests the getDefinition() method.
   *
   * @covers ::getDefinition
   *
   * @dataProvider providerTestGetDefinition
   */
  public function testGetDefinition($entity_type_id, $expected) {
    $entity = $this
      ->prophesize(EntityTypeInterface::class);
    $this
      ->setUpEntityTypeDefinitions([
      'apple' => $entity,
      'banana' => $entity,
    ]);
    $entity_type = $this->entityTypeManager
      ->getDefinition($entity_type_id, FALSE);
    if ($expected) {
      $this
        ->assertInstanceOf(EntityTypeInterface::class, $entity_type);
    }
    else {
      $this
        ->assertNull($entity_type);
    }
  }

  /**
   * Provides test data for testGetDefinition().
   *
   * @return array
   *   Test data.
   */
  public function providerTestGetDefinition() {
    return [
      [
        'apple',
        TRUE,
      ],
      [
        'banana',
        TRUE,
      ],
      [
        'pear',
        FALSE,
      ],
    ];
  }

  /**
   * Tests the getDefinition() method with an invalid definition.
   *
   * @covers ::getDefinition
   */
  public function testGetDefinitionInvalidException() {
    $this
      ->setUpEntityTypeDefinitions();
    $this
      ->expectException(PluginNotFoundException::class);
    $this
      ->expectExceptionMessage('The "pear" entity type does not exist.');
    $this->entityTypeManager
      ->getDefinition('pear', TRUE);
  }

  /**
   * Gets a mock controller class name.
   *
   * @return string
   *   A mock controller class name.
   */
  protected function getTestHandlerClass() {
    return get_class($this
      ->getMockForAbstractClass(EntityHandlerBase::class));
  }

}

/**
 * Provides a test entity handler.
 */
abstract class TestEntityHandlerBase extends EntityHandlerBase {

  /**
   * {@inheritdoc}
   */
  public $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public $stringTranslation;

}

/**
 * Provides a test entity type manager.
 */
class TestEntityTypeManager extends EntityTypeManager {

  /**
   * Sets the discovery for the manager.
   *
   * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
   *   The discovery object.
   */
  public function setDiscovery(DiscoveryInterface $discovery) {
    $this->discovery = $discovery;
  }

}

/**
 * Provides a test entity form.
 */
class TestEntityForm extends EntityHandlerBase {

  /**
   * {@inheritdoc}
   */
  public $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public $stringTranslation;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * {@inheritdoc}
   */
  public function getBaseFormId() {
    return 'the_base_form_id';
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'the_form_id';
  }

  /**
   * {@inheritdoc}
   */
  public function setEntity(EntityInterface $entity) {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setOperation($operation) {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
    return $this;
  }

}

/**
 * Provides a test entity form that uses injection.
 */
class TestEntityFormInjected extends TestEntityForm implements ContainerInjectionInterface {

  /**
   * The color of the entity type.
   *
   * @var string
   */
  public $color;

  /**
   * Constructs a new TestEntityFormInjected.
   *
   * @param string $color
   *   The color of the entity type.
   */
  public function __construct($color) {
    $this->color = $color;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static('yellow');
  }

}

/**
 * Provides a test entity route provider.
 */
class TestRouteProvider extends EntityHandlerBase {

  /**
   * {@inheritdoc}
   */
  public $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public $stringTranslation;

}

Classes

Namesort descending Description
EntityTypeManagerTest @coversDefaultClass \Drupal\Core\Entity\EntityTypeManager @group Entity
TestEntityForm Provides a test entity form.
TestEntityFormInjected Provides a test entity form that uses injection.
TestEntityHandlerBase Provides a test entity handler.
TestEntityTypeManager Provides a test entity type manager.
TestRouteProvider Provides a test entity route provider.