EntityFieldManagerTest.php

Same filename in other branches
  1. 9 core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php
  2. 8.9.x core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php
  3. 10 core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php

Namespace

Drupal\Tests\Core\Entity

File

core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\Core\Entity;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManager;
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;
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\BooleanItem;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @coversDefaultClass \Drupal\Core\Entity\EntityFieldManager
 * @group Entity
 */
class EntityFieldManagerTest extends UnitTestCase {
    
    /**
     * The typed data manager.
     *
     * @var \Drupal\Core\TypedData\TypedDataManagerInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $typedDataManager;
    
    /**
     * The module handler.
     *
     * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $moduleHandler;
    
    /**
     * The cache backend to use.
     *
     * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $cacheBackend;
    
    /**
     * The cache tags invalidator.
     *
     * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $cacheTagsInvalidator;
    
    /**
     * The language manager.
     *
     * @var \Drupal\Core\Language\LanguageManagerInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $languageManager;
    
    /**
     * The keyvalue factory.
     *
     * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $keyValueFactory;
    
    /**
     * The event dispatcher.
     *
     * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $eventDispatcher;
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $entityTypeManager;
    
    /**
     * The entity type repository.
     *
     * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $entityTypeRepository;
    
    /**
     * The entity type bundle info.
     *
     * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $entityTypeBundleInfo;
    
    /**
     * The entity display repository.
     *
     * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $entityDisplayRepository;
    
    /**
     * The entity field manager under test.
     *
     * @var \Drupal\Core\Entity\EntityFieldManager
     */
    protected $entityFieldManager;
    
    /**
     * The dependency injection container.
     *
     * @var \Symfony\Component\DependencyInjection\ContainerInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $container;
    
    /**
     * The entity type definition.
     *
     * @var \Drupal\Core\Entity\EntityTypeInterface|\Prophecy\Prophecy\ProphecyInterface
     */
    protected $entityType;
    
    /**
     * The entity last installed schema repository.
     *
     * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
     */
    protected $entityLastInstalledSchemaRepository;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $this->container = $this->prophesize(ContainerInterface::class);
        \Drupal::setContainer($this->container
            ->reveal());
        $this->typedDataManager = $this->prophesize(TypedDataManagerInterface::class);
        $this->typedDataManager
            ->getDefinition('field_item:boolean')
            ->willReturn([
            'class' => BooleanItem::class,
        ]);
        $this->container
            ->get('typed_data_manager')
            ->willReturn($this->typedDataManager
            ->reveal());
        $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
        $this->moduleHandler
            ->alter('entity_base_field_info', Argument::type('array'), Argument::any())
            ->willReturn(NULL);
        $this->moduleHandler
            ->alter('entity_bundle_field_info', Argument::type('array'), Argument::any(), Argument::type('string'))
            ->willReturn(NULL);
        $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
        $this->cacheTagsInvalidator = $this->prophesize(CacheTagsInvalidatorInterface::class);
        $language = new Language([
            'id' => 'en',
        ]);
        $this->languageManager = $this->prophesize(LanguageManagerInterface::class);
        $this->languageManager
            ->getCurrentLanguage()
            ->willReturn($language);
        $this->languageManager
            ->getLanguages()
            ->willReturn([
            'en' => (object) [
                'id' => 'en',
            ],
        ]);
        $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class);
        $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
        $this->entityTypeRepository = $this->prophesize(EntityTypeRepositoryInterface::class);
        $this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
        $this->entityDisplayRepository = $this->prophesize(EntityDisplayRepositoryInterface::class);
        $this->entityLastInstalledSchemaRepository = $this->prophesize(EntityLastInstalledSchemaRepositoryInterface::class);
        $this->entityFieldManager = new TestEntityFieldManager($this->entityTypeManager
            ->reveal(), $this->entityTypeBundleInfo
            ->reveal(), $this->entityDisplayRepository
            ->reveal(), $this->typedDataManager
            ->reveal(), $this->languageManager
            ->reveal(), $this->keyValueFactory
            ->reveal(), $this->moduleHandler
            ->reveal(), $this->cacheBackend
            ->reveal(), $this->entityLastInstalledSchemaRepository
            ->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 = []) : void {
        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([]);
            $definitions[$key] = $entity_type->reveal();
        }
        $this->entityTypeManager
            ->getDefinition(Argument::type('string'))
            ->will(function ($args) use ($definitions) {
            if (isset($definitions[$args[0]])) {
                return $definitions[$args[0]];
            }
            throw new PluginNotFoundException($args[0]);
        });
        $this->entityTypeManager
            ->getDefinition(Argument::type('string'), FALSE)
            ->will(function ($args) use ($definitions) {
            if (isset($definitions[$args[0]])) {
                return $definitions[$args[0]];
            }
        });
        $this->entityTypeManager
            ->getDefinitions()
            ->willReturn($definitions);
    }
    
    /**
     * Tests the getBaseFieldDefinitions() method.
     *
     * @covers ::getBaseFieldDefinitions
     * @covers ::buildBaseFieldDefinitions
     */
    public function testGetBaseFieldDefinitions() : void {
        $field_definition = $this->setUpEntityWithFieldDefinition();
        $expected = [
            'id' => $field_definition,
        ];
        $this->assertSame($expected, $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type'));
    }
    
    /**
     * Tests the getFieldDefinitions() method.
     *
     * @covers ::getFieldDefinitions
     * @covers ::buildBundleFieldDefinitions
     */
    public function testGetFieldDefinitions() : void {
        $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'));
    }
    
    /**
     * Tests the getFieldStorageDefinitions() method.
     *
     * @covers ::getFieldStorageDefinitions
     * @covers ::buildFieldStorageDefinitions
     */
    public function testGetFieldStorageDefinitions() : void {
        $field_definition = $this->setUpEntityWithFieldDefinition(TRUE);
        $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
        $field_storage_definition->getName()
            ->willReturn('field_storage');
        $base_field_definition = $this->prophesize(BaseFieldDefinition::class);
        $base_field_definition->setProvider('example_module')
            ->shouldBeCalled();
        $base_field_definition->setName('base_field')
            ->shouldBeCalled();
        $base_field_definition->setTargetEntityTypeId('test_entity_type')
            ->shouldBeCalled();
        $definitions = [
            'base_field' => $base_field_definition->reveal(),
            'field_storage' => $field_storage_definition->reveal(),
        ];
        $this->moduleHandler
            ->invokeAllWith('entity_base_field_info', Argument::any());
        $this->moduleHandler
            ->invokeAllWith('entity_field_storage_info', Argument::any())
            ->will(function ($arguments) use ($definitions) {
            [
                ,
                $callback,
            ] = $arguments;
            $callback(function () use ($definitions) {
                return $definitions;
            }, 'example_module');
        });
        $this->moduleHandler
            ->alter('entity_field_storage_info', $definitions, $this->entityType)
            ->willReturn(NULL);
        $expected = [
            'id' => $field_definition,
            'base_field' => $base_field_definition->reveal(),
            'field_storage' => $field_storage_definition->reveal(),
        ];
        $this->assertSame($expected, $this->entityFieldManager
            ->getFieldStorageDefinitions('test_entity_type'));
    }
    
    /**
     * Tests the getBaseFieldDefinitions() method with a translatable entity type.
     *
     * @covers ::getBaseFieldDefinitions
     * @covers ::buildBaseFieldDefinitions
     *
     * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode
     */
    public function testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode($default_langcode_key) : void {
        $this->setUpEntityWithFieldDefinition(FALSE, 'id', [
            'langcode' => 'langcode',
            'default_langcode' => $default_langcode_key,
        ]);
        $field_definition = $this->prophesize()
            ->willImplement(FieldDefinitionInterface::class)
            ->willImplement(FieldStorageDefinitionInterface::class);
        $field_definition->isTranslatable()
            ->willReturn(TRUE);
        $entity_class = EntityTypeManagerTestEntity::class;
        $entity_class::$baseFieldDefinitions += [
            'langcode' => $field_definition,
        ];
        $this->entityType
            ->isTranslatable()
            ->willReturn(TRUE);
        $definitions = $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type');
        $this->assertTrue(isset($definitions[$default_langcode_key]));
    }
    
    /**
     * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode().
     *
     * @return array
     *   Test data.
     */
    public static function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode() {
        return [
            [
                'default_langcode',
            ],
            [
                'custom_default_langcode_key',
            ],
        ];
    }
    
    /**
     * Tests the getBaseFieldDefinitions() method with a translatable entity type.
     *
     * @covers ::getBaseFieldDefinitions
     * @covers ::buildBaseFieldDefinitions
     *
     * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode
     */
    public function testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode($provide_key, $provide_field, $translatable) : void {
        $keys = $provide_key ? [
            'langcode' => 'langcode',
        ] : [];
        $this->setUpEntityWithFieldDefinition(FALSE, 'id', $keys);
        if ($provide_field) {
            $field_definition = $this->prophesize()
                ->willImplement(FieldDefinitionInterface::class)
                ->willImplement(FieldStorageDefinitionInterface::class);
            $field_definition->isTranslatable()
                ->willReturn($translatable);
            if (!$translatable) {
                $field_definition->setTranslatable(!$translatable)
                    ->shouldBeCalled();
            }
            $entity_class = EntityTypeManagerTestEntity::class;
            $entity_class::$baseFieldDefinitions += [
                'langcode' => $field_definition->reveal(),
            ];
        }
        $this->entityType
            ->isTranslatable()
            ->willReturn(TRUE);
        $this->entityType
            ->getLabel()
            ->willReturn('Test');
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The Test entity type cannot be translatable as it does not define a translatable "langcode" field.');
        $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type');
    }
    
    /**
     * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode().
     *
     * @return array
     *   Test data.
     */
    public static function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode() {
        return [
            [
                FALSE,
                TRUE,
                TRUE,
            ],
            [
                TRUE,
                FALSE,
                TRUE,
            ],
            [
                TRUE,
                TRUE,
                FALSE,
            ],
        ];
    }
    
    /**
     * Tests the getBaseFieldDefinitions() method with caching.
     *
     * @covers ::getBaseFieldDefinitions
     */
    public function testGetBaseFieldDefinitionsWithCaching() : void {
        $field_definition = $this->setUpEntityWithFieldDefinition();
        $expected = [
            'id' => $field_definition,
        ];
        $cacheBackend = $this->cacheBackend;
        $this->cacheBackend
            ->get('entity_base_field_definitions:test_entity_type:en')
            ->willReturn(FALSE)
            ->shouldBeCalled();
        $this->cacheBackend
            ->set('entity_base_field_definitions:test_entity_type:en', Argument::any(), Cache::PERMANENT, [
            'entity_types',
            'entity_field_info',
        ])
            ->will(function (array $args) use ($cacheBackend) {
            $data = (object) [
                'data' => $args[1],
            ];
            $cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
                ->willReturn($data)
                ->shouldBeCalled();
        })
            ->shouldBeCalled();
        $this->assertSame($expected, $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type'));
        $this->entityFieldManager
            ->testClearEntityFieldInfo();
        $this->assertSame($expected, $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type'));
    }
    
    /**
     * Tests the getFieldDefinitions() method with caching.
     *
     * @covers ::getFieldDefinitions
     */
    public function testGetFieldDefinitionsWithCaching() : void {
        $field_definition = $this->setUpEntityWithFieldDefinition(FALSE, 'id');
        $expected = [
            'id' => $field_definition,
        ];
        $cacheBackend = $this->cacheBackend;
        $this->cacheBackend
            ->get('entity_base_field_definitions:test_entity_type:en')
            ->willReturn((object) [
            'data' => $expected,
        ])
            ->shouldBeCalledTimes(2);
        $this->cacheBackend
            ->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
            ->willReturn(FALSE)
            ->shouldBeCalledTimes(1);
        $this->cacheBackend
            ->set('entity_bundle_field_definitions:test_entity_type:test_bundle:en', Argument::any(), Cache::PERMANENT, [
            'entity_types',
            'entity_field_info',
        ])
            ->will(function (array $args) use ($cacheBackend) {
            $data = (object) [
                'data' => $args[1],
            ];
            $cacheBackend->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
                ->willReturn($data)
                ->shouldBeCalled();
        })
            ->shouldBeCalled();
        $this->assertSame($expected, $this->entityFieldManager
            ->getFieldDefinitions('test_entity_type', 'test_bundle'));
        $this->entityFieldManager
            ->testClearEntityFieldInfo();
        $this->assertSame($expected, $this->entityFieldManager
            ->getFieldDefinitions('test_entity_type', 'test_bundle'));
    }
    
    /**
     * Tests the getFieldStorageDefinitions() method with caching.
     *
     * @covers ::getFieldStorageDefinitions
     */
    public function testGetFieldStorageDefinitionsWithCaching() : void {
        $field_definition = $this->setUpEntityWithFieldDefinition(TRUE, 'id');
        $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
        $field_storage_definition->getName()
            ->willReturn('field_storage');
        $definitions = [
            'field_storage' => $field_storage_definition->reveal(),
        ];
        $this->moduleHandler
            ->invokeAllWith('entity_field_storage_info', Argument::any())
            ->will(function ($arguments) use ($definitions) {
            [
                ,
                $callback,
            ] = $arguments;
            $callback(function () use ($definitions) {
                return $definitions;
            }, 'example_module');
        });
        $this->moduleHandler
            ->alter('entity_field_storage_info', $definitions, $this->entityType)
            ->willReturn(NULL);
        $expected = [
            'id' => $field_definition,
            'field_storage' => $field_storage_definition->reveal(),
        ];
        $cacheBackend = $this->cacheBackend;
        $this->cacheBackend
            ->get('entity_base_field_definitions:test_entity_type:en')
            ->willReturn((object) [
            'data' => [
                'id' => $expected['id'],
            ],
        ])
            ->shouldBeCalledTimes(2);
        $this->cacheBackend
            ->get('entity_field_storage_definitions:test_entity_type:en')
            ->willReturn(FALSE);
        $this->cacheBackend
            ->set('entity_field_storage_definitions:test_entity_type:en', Argument::any(), Cache::PERMANENT, [
            'entity_types',
            'entity_field_info',
        ])
            ->will(function () use ($expected, $cacheBackend) {
            $cacheBackend->get('entity_field_storage_definitions:test_entity_type:en')
                ->willReturn((object) [
                'data' => $expected,
            ])
                ->shouldBeCalled();
        })
            ->shouldBeCalled();
        $this->assertSame($expected, $this->entityFieldManager
            ->getFieldStorageDefinitions('test_entity_type'));
        $this->entityFieldManager
            ->testClearEntityFieldInfo();
        $this->assertSame($expected, $this->entityFieldManager
            ->getFieldStorageDefinitions('test_entity_type'));
    }
    
    /**
     * Tests the getBaseFieldDefinitions() method with an invalid definition.
     *
     * @covers ::getBaseFieldDefinitions
     * @covers ::buildBaseFieldDefinitions
     */
    public function testGetBaseFieldDefinitionsInvalidDefinition() : void {
        $this->setUpEntityWithFieldDefinition(FALSE, 'langcode', [
            'langcode' => 'langcode',
        ]);
        $this->entityType
            ->isTranslatable()
            ->willReturn(TRUE);
        $this->entityType
            ->getLabel()
            ->willReturn('the_label');
        $this->expectException(\LogicException::class);
        $this->entityFieldManager
            ->getBaseFieldDefinitions('test_entity_type');
    }
    
    /**
     * Tests that getFieldDefinitions() method sets the 'provider' definition key.
     *
     * @covers ::getFieldDefinitions
     * @covers ::buildBundleFieldDefinitions
     */
    public function testGetFieldDefinitionsProvider() : void {
        $this->setUpEntityWithFieldDefinition(TRUE);
        $module = 'entity_field_manager_test_module';
        // @todo Mock FieldDefinitionInterface once it exposes a proper provider
        //   setter. See https://www.drupal.org/node/2346329.
        $field_definition = $this->prophesize(BaseFieldDefinition::class);
        // We expect two calls as the field definition will be returned from both
        // base and bundle entity field info hook implementations.
        $field_definition->getProvider()
            ->shouldBeCalled();
        $field_definition->setProvider($module)
            ->shouldBeCalledTimes(2);
        $field_definition->setName(0)
            ->shouldBeCalledTimes(2);
        $field_definition->setTargetEntityTypeId('test_entity_type')
            ->shouldBeCalled();
        $field_definition->setTargetBundle(NULL)
            ->shouldBeCalled();
        $field_definition->setTargetBundle('test_bundle')
            ->shouldBeCalled();
        $this->moduleHandler
            ->invokeAllWith(Argument::type('string'), Argument::any())
            ->will(function ($arguments) use ($field_definition, $module) {
            [
                ,
                $callback,
            ] = $arguments;
            $callback(function () use ($field_definition) {
                return [
                    $field_definition->reveal(),
                ];
            }, $module);
        });
        $this->entityFieldManager
            ->getFieldDefinitions('test_entity_type', 'test_bundle');
    }
    
    /**
     * Prepares an entity that defines a field definition.
     *
     * @param bool $custom_invoke_all
     *   (optional) Whether the test will set up its own
     *   ModuleHandlerInterface::invokeAll() implementation. Defaults to FALSE.
     * @param string $field_definition_id
     *   (optional) The ID to use for the field definition. Defaults to 'id'.
     * @param array $entity_keys
     *   (optional) An array of entity keys for the mocked entity type. Defaults
     *   to an empty array.
     *
     * @return \Drupal\Core\Field\BaseFieldDefinition|\Prophecy\Prophecy\ProphecyInterface
     *   A field definition object.
     */
    protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $field_definition_id = 'id', $entity_keys = []) {
        $field_type_manager = $this->prophesize(FieldTypePluginManagerInterface::class);
        $field_type_manager->getDefaultStorageSettings('boolean')
            ->willReturn([]);
        $field_type_manager->getDefaultFieldSettings('boolean')
            ->willReturn([]);
        $this->container
            ->get('plugin.manager.field.field_type')
            ->willReturn($field_type_manager->reveal());
        $string_translation = $this->prophesize(TranslationInterface::class);
        $this->container
            ->get('string_translation')
            ->willReturn($string_translation->reveal());
        $entity_class = EntityTypeManagerTestEntity::class;
        $field_definition = $this->prophesize()
            ->willImplement(FieldDefinitionInterface::class)
            ->willImplement(FieldStorageDefinitionInterface::class);
        $entity_class::$baseFieldDefinitions = [
            $field_definition_id => $field_definition->reveal(),
        ];
        $entity_class::$bundleFieldDefinitions = [];
        if (!$custom_invoke_all) {
            $this->moduleHandler
                ->invokeAllWith(Argument::cetera(), Argument::cetera());
        }
        // Mock the base field definition override.
        $override_entity_type = $this->prophesize(EntityTypeInterface::class);
        $this->entityType = $this->prophesize(EntityTypeInterface::class);
        $this->setUpEntityTypeDefinitions([
            'test_entity_type' => $this->entityType,
            'base_field_override' => $override_entity_type,
        ]);
        $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);
        $this->entityType
            ->getKeys()
            ->willReturn($entity_keys + [
            'default_langcode' => 'default_langcode',
        ]);
        $this->entityType
            ->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(TRUE);
        $this->entityType
            ->isTranslatable()
            ->willReturn(FALSE);
        $this->entityType
            ->isRevisionable()
            ->willReturn(FALSE);
        $this->entityType
            ->getProvider()
            ->willReturn('the_provider');
        $this->entityType
            ->id()
            ->willReturn('the_entity_id');
        return $field_definition->reveal();
    }
    
    /**
     * Tests the clearCachedFieldDefinitions() method.
     *
     * @covers ::clearCachedFieldDefinitions
     */
    public function testClearCachedFieldDefinitions() : void {
        $this->setUpEntityTypeDefinitions();
        $this->cacheTagsInvalidator
            ->invalidateTags([
            'entity_field_info',
        ])
            ->shouldBeCalled();
        $this->container
            ->get('cache_tags.invalidator')
            ->willReturn($this->cacheTagsInvalidator
            ->reveal())
            ->shouldBeCalled();
        $this->typedDataManager
            ->clearCachedDefinitions()
            ->shouldBeCalled();
        $this->entityFieldManager
            ->clearCachedFieldDefinitions();
    }
    
    /**
     * @covers ::getExtraFields
     */
    public function testGetExtraFields() : void {
        $this->setUpEntityTypeDefinitions();
        $entity_type_id = $this->randomMachineName();
        $bundle = $this->randomMachineName();
        $language_code = 'en';
        $hook_bundle_extra_fields = [
            $entity_type_id => [
                $bundle => [
                    'form' => [
                        'foo_extra_field' => [
                            'label' => 'Foo',
                        ],
                    ],
                ],
            ],
        ];
        $processed_hook_bundle_extra_fields = $hook_bundle_extra_fields;
        $processed_hook_bundle_extra_fields[$entity_type_id][$bundle] += [
            'display' => [],
        ];
        $cache_id = 'entity_extra_field_info:' . $language_code;
        $language = new Language([
            'id' => $language_code,
        ]);
        $this->languageManager
            ->getCurrentLanguage()
            ->willReturn($language)
            ->shouldBeCalledTimes(1);
        $this->cacheBackend
            ->get($cache_id)
            ->shouldBeCalled();
        $this->moduleHandler
            ->invokeAll('entity_extra_field_info')
            ->willReturn($hook_bundle_extra_fields);
        $this->moduleHandler
            ->alter('entity_extra_field_info', $hook_bundle_extra_fields)
            ->shouldBeCalled();
        $this->cacheBackend
            ->set($cache_id, $processed_hook_bundle_extra_fields, Cache::PERMANENT, [
            'entity_field_info',
        ])
            ->shouldBeCalled();
        $this->assertSame($processed_hook_bundle_extra_fields[$entity_type_id][$bundle], $this->entityFieldManager
            ->getExtraFields($entity_type_id, $bundle));
    }
    
    /**
     * @covers ::getFieldMap
     */
    public function testGetFieldMap() : void {
        $this->entityTypeBundleInfo
            ->getBundleInfo('test_entity_type')
            ->willReturn([])
            ->shouldBeCalled();
        // Set up a content entity type.
        $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
        $entity_class = EntityTypeManagerTestEntity::class;
        // Define an ID field definition as a base field.
        $id_definition = $this->prophesize(FieldDefinitionInterface::class);
        $id_definition->getType()
            ->willReturn('integer');
        $base_field_definitions = [
            'id' => $id_definition->reveal(),
        ];
        $entity_class::$baseFieldDefinitions = $base_field_definitions;
        // Set up the stored bundle field map.
        $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
        $this->keyValueFactory
            ->get('entity.definitions.bundle_field_map')
            ->willReturn($key_value_store->reveal());
        $key_value_store->getAll()
            ->willReturn([
            'test_entity_type' => [
                'by_bundle' => [
                    'type' => 'string',
                    'bundles' => [
                        'second_bundle' => 'second_bundle',
                    ],
                ],
            ],
        ]);
        // Set up a non-content entity type.
        $non_content_entity_type = $this->prophesize(EntityTypeInterface::class);
        // Mock the base field definition override.
        $override_entity_type = $this->prophesize(EntityTypeInterface::class);
        $this->setUpEntityTypeDefinitions([
            'test_entity_type' => $entity_type,
            'non_fieldable' => $non_content_entity_type,
            'base_field_override' => $override_entity_type,
        ]);
        $entity_type->getClass()
            ->willReturn($entity_class);
        $entity_type->getKeys()
            ->willReturn([
            'default_langcode' => 'default_langcode',
        ]);
        $entity_type->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(TRUE);
        $entity_type->isTranslatable()
            ->shouldBeCalled();
        $entity_type->isRevisionable()
            ->shouldBeCalled();
        $entity_type->getProvider()
            ->shouldBeCalled();
        $non_content_entity_type->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(FALSE);
        $override_entity_type->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(FALSE);
        // Set up the entity type bundle info to return two bundles for the
        // fieldable entity type.
        $this->entityTypeBundleInfo
            ->getBundleInfo('test_entity_type')
            ->willReturn([
            'first_bundle' => 'first_bundle',
            'second_bundle' => 'second_bundle',
        ])
            ->shouldBeCalled();
        $this->moduleHandler
            ->invokeAllWith('entity_base_field_info', Argument::any());
        $expected = [
            'test_entity_type' => [
                'id' => [
                    'type' => 'integer',
                    'bundles' => [
                        'first_bundle' => 'first_bundle',
                        'second_bundle' => 'second_bundle',
                    ],
                ],
                'by_bundle' => [
                    'type' => 'string',
                    'bundles' => [
                        'second_bundle' => 'second_bundle',
                    ],
                ],
            ],
        ];
        $this->assertEquals($expected, $this->entityFieldManager
            ->getFieldMap());
    }
    
    /**
     * @covers ::getFieldMap
     */
    public function testGetFieldMapFromCache() : void {
        $expected = [
            'test_entity_type' => [
                'id' => [
                    'type' => 'integer',
                    'bundles' => [
                        'first_bundle' => 'first_bundle',
                        'second_bundle' => 'second_bundle',
                    ],
                ],
                'by_bundle' => [
                    'type' => 'string',
                    'bundles' => [
                        'second_bundle' => 'second_bundle',
                    ],
                ],
            ],
        ];
        $this->setUpEntityTypeDefinitions();
        $this->cacheBackend
            ->get('entity_field_map')
            ->willReturn((object) [
            'data' => $expected,
        ]);
        // Call the field map twice to make sure the static cache works.
        $this->assertEquals($expected, $this->entityFieldManager
            ->getFieldMap());
        $this->assertEquals($expected, $this->entityFieldManager
            ->getFieldMap());
    }
    
    /**
     * @covers ::getFieldMapByFieldType
     */
    public function testGetFieldMapByFieldType() : void {
        // Set up a content entity type.
        $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
        $entity_class = EntityTypeManagerTestEntity::class;
        // Set up the entity type bundle info to return two bundles for the
        // fieldable entity type.
        $this->entityTypeBundleInfo
            ->getBundleInfo('test_entity_type')
            ->willReturn([
            'first_bundle' => 'first_bundle',
            'second_bundle' => 'second_bundle',
        ])
            ->shouldBeCalled();
        $this->moduleHandler
            ->invokeAllWith('entity_base_field_info', Argument::any())
            ->shouldBeCalled();
        // Define an ID field definition as a base field.
        $id_definition = $this->prophesize(FieldDefinitionInterface::class);
        $id_definition->getType()
            ->willReturn('integer')
            ->shouldBeCalled();
        $base_field_definitions = [
            'id' => $id_definition->reveal(),
        ];
        $entity_class::$baseFieldDefinitions = $base_field_definitions;
        // Set up the stored bundle field map.
        $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
        $this->keyValueFactory
            ->get('entity.definitions.bundle_field_map')
            ->willReturn($key_value_store->reveal())
            ->shouldBeCalled();
        $key_value_store->getAll()
            ->willReturn([
            'test_entity_type' => [
                'by_bundle' => [
                    'type' => 'string',
                    'bundles' => [
                        'second_bundle' => 'second_bundle',
                    ],
                ],
            ],
        ])
            ->shouldBeCalled();
        // Mock the base field definition override.
        $override_entity_type = $this->prophesize(EntityTypeInterface::class);
        $this->setUpEntityTypeDefinitions([
            'test_entity_type' => $entity_type,
            'base_field_override' => $override_entity_type,
        ]);
        $entity_type->getClass()
            ->willReturn($entity_class)
            ->shouldBeCalled();
        $entity_type->getKeys()
            ->willReturn([
            'default_langcode' => 'default_langcode',
        ])
            ->shouldBeCalled();
        $entity_type->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(TRUE)
            ->shouldBeCalled();
        $entity_type->isTranslatable()
            ->shouldBeCalled();
        $entity_type->isRevisionable()
            ->shouldBeCalled();
        $entity_type->getProvider()
            ->shouldBeCalled();
        $override_entity_type->entityClassImplements(FieldableEntityInterface::class)
            ->willReturn(FALSE)
            ->shouldBeCalled();
        $integerFields = $this->entityFieldManager
            ->getFieldMapByFieldType('integer');
        $this->assertCount(1, $integerFields['test_entity_type']);
        $this->assertArrayNotHasKey('non_fieldable', $integerFields);
        $this->assertArrayHasKey('id', $integerFields['test_entity_type']);
        $this->assertArrayNotHasKey('by_bundle', $integerFields['test_entity_type']);
        $stringFields = $this->entityFieldManager
            ->getFieldMapByFieldType('string');
        $this->assertCount(1, $stringFields['test_entity_type']);
        $this->assertArrayNotHasKey('non_fieldable', $stringFields);
        $this->assertArrayHasKey('by_bundle', $stringFields['test_entity_type']);
        $this->assertArrayNotHasKey('id', $stringFields['test_entity_type']);
    }

}

/**
 * Stub class for testing EntityFieldManager.
 */
class TestEntityFieldManager extends EntityFieldManager {
    
    /**
     * Allows the static caches to be cleared.
     */
    public function testClearEntityFieldInfo() : void {
        $this->baseFieldDefinitions = [];
        $this->fieldDefinitions = [];
        $this->fieldStorageDefinitions = [];
    }

}

/**
 * Provides a content entity with dummy static method implementations.
 */
abstract class EntityTypeManagerTestEntity implements \Iterator, ContentEntityInterface {
    
    /**
     * The base field definitions.
     *
     * @var \Drupal\Core\Field\FieldDefinitionInterface[]
     */
    public static $baseFieldDefinitions = [];
    
    /**
     * The bundle 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 $bundleFieldDefinitions = [];
    
    /**
     * {@inheritdoc}
     */
    public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
        return static::$baseFieldDefinitions;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
        return static::$bundleFieldDefinitions[$entity_type->id()][$bundle] ?? [];
    }

}

/**
 * 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;
    }

}

Classes

Title Deprecated Summary
EntityFieldManagerTest @coversDefaultClass \Drupal\Core\Entity\EntityFieldManager @group Entity
EntityTypeManagerTestEntity Provides a content entity with dummy static method implementations.
EntityTypeManagerTestEntityBundle Provides a bundle specific class with dummy static method implementations.
TestEntityFieldManager Stub class for testing EntityFieldManager.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.