5 * Contains \Drupal\Tests\Core\Entity\EntityFieldManagerTest.
8 namespace Drupal\Tests\Core\Entity;
10 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
11 use Drupal\Core\Cache\Cache;
12 use Drupal\Core\Cache\CacheBackendInterface;
13 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
14 use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
15 use Drupal\Core\Entity\ContentEntityInterface;
16 use Drupal\Core\Entity\ContentEntityTypeInterface;
17 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
18 use Drupal\Core\Entity\EntityFieldManager;
19 use Drupal\Core\Entity\EntityInterface;
20 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
21 use Drupal\Core\Entity\EntityTypeInterface;
22 use Drupal\Core\Entity\EntityTypeManagerInterface;
23 use Drupal\Core\Entity\EntityTypeRepositoryInterface;
24 use Drupal\Core\Entity\FieldableEntityInterface;
25 use Drupal\Core\Extension\ModuleHandlerInterface;
26 use Drupal\Core\Field\BaseFieldDefinition;
27 use Drupal\Core\Field\FieldDefinitionInterface;
28 use Drupal\Core\Field\FieldStorageDefinitionInterface;
29 use Drupal\Core\Field\FieldTypePluginManagerInterface;
30 use Drupal\Core\Field\Plugin\Field\FieldType\BooleanItem;
31 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
32 use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
33 use Drupal\Core\Language\Language;
34 use Drupal\Core\Language\LanguageManagerInterface;
35 use Drupal\Core\StringTranslation\TranslationInterface;
36 use Drupal\Core\TypedData\TypedDataManagerInterface;
37 use Drupal\Tests\UnitTestCase;
38 use Prophecy\Argument;
39 use Symfony\Component\DependencyInjection\ContainerInterface;
42 * @coversDefaultClass \Drupal\Core\Entity\EntityFieldManager
45 class EntityFieldManagerTest extends UnitTestCase {
48 * The typed data manager.
50 * @var \Drupal\Core\TypedData\TypedDataManagerInterface|\Prophecy\Prophecy\ProphecyInterface
52 protected $typedDataManager;
57 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
59 protected $moduleHandler;
62 * The cache backend to use.
64 * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
66 protected $cacheBackend;
69 * The cache tags invalidator.
71 * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\Prophecy\Prophecy\ProphecyInterface
73 protected $cacheTagsInvalidator;
76 * The language manager.
78 * @var \Drupal\Core\Language\LanguageManagerInterface|\Prophecy\Prophecy\ProphecyInterface
80 protected $languageManager;
83 * The keyvalue factory.
85 * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
87 protected $keyValueFactory;
90 * The event dispatcher.
92 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\Prophecy\Prophecy\ProphecyInterface
94 protected $eventDispatcher;
97 * The entity type manager.
99 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
101 protected $entityTypeManager;
104 * The entity type repository.
106 * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
108 protected $entityTypeRepository;
111 * The entity type bundle info.
113 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
115 protected $entityTypeBundleInfo;
118 * The entity display repository.
120 * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
122 protected $entityDisplayRepository;
125 * The entity field manager under test.
127 * @var \Drupal\Core\Entity\EntityFieldManager
129 protected $entityFieldManager;
132 * The dependency injection container.
134 * @var \Symfony\Component\DependencyInjection\ContainerInterface|\Prophecy\Prophecy\ProphecyInterface
136 protected $container;
139 * The entity type definition.
141 * @var \Drupal\Core\Entity\EntityTypeInterface|\Prophecy\Prophecy\ProphecyInterface
143 protected $entityType;
148 protected function setUp() {
151 $this->container = $this->prophesize(ContainerInterface::class);
152 \Drupal::setContainer($this->container->reveal());
154 $this->typedDataManager = $this->prophesize(TypedDataManagerInterface::class);
155 $this->typedDataManager->getDefinition('field_item:boolean')->willReturn([
156 'class' => BooleanItem::class,
158 $this->container->get('typed_data_manager')->willReturn($this->typedDataManager->reveal());
160 $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
161 $this->moduleHandler->alter('entity_base_field_info', Argument::type('array'), Argument::any())->willReturn(NULL);
162 $this->moduleHandler->alter('entity_bundle_field_info', Argument::type('array'), Argument::any(), Argument::type('string'))->willReturn(NULL);
164 $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
165 $this->cacheTagsInvalidator = $this->prophesize(CacheTagsInvalidatorInterface::class);
167 $language = new Language(['id' => 'en']);
168 $this->languageManager = $this->prophesize(LanguageManagerInterface::class);
169 $this->languageManager->getCurrentLanguage()->willReturn($language);
170 $this->languageManager->getLanguages()->willReturn(['en' => (object) ['id' => 'en']]);
172 $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class);
174 $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
175 $this->entityTypeRepository = $this->prophesize(EntityTypeRepositoryInterface::class);
176 $this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
177 $this->entityDisplayRepository = $this->prophesize(EntityDisplayRepositoryInterface::class);
179 $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());
183 * Sets up the entity type manager to be tested.
185 * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
186 * (optional) An array of entity type definitions.
188 protected function setUpEntityTypeDefinitions($definitions = []) {
189 $class = $this->getMockClass(EntityInterface::class);
190 foreach ($definitions as $key => $entity_type) {
191 // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
192 // by \Drupal\Core\Entity\EntityManager::processDefinition() so it must
194 $entity_type->getLinkTemplates()->willReturn([]);
196 // Give the entity type a legitimate class to return.
197 $entity_type->getClass()->willReturn($class);
199 $definitions[$key] = $entity_type->reveal();
202 $this->entityTypeManager->getDefinition(Argument::type('string'))
203 ->will(function ($args) use ($definitions) {
204 if (isset($definitions[$args[0]])) {
205 return $definitions[$args[0]];
207 throw new PluginNotFoundException($args[0]);
209 $this->entityTypeManager->getDefinition(Argument::type('string'), FALSE)
210 ->will(function ($args) use ($definitions) {
211 if (isset($definitions[$args[0]])) {
212 return $definitions[$args[0]];
215 $this->entityTypeManager->getDefinitions()->willReturn($definitions);
220 * Tests the getBaseFieldDefinitions() method.
222 * @covers ::getBaseFieldDefinitions
223 * @covers ::buildBaseFieldDefinitions
225 public function testGetBaseFieldDefinitions() {
226 $field_definition = $this->setUpEntityWithFieldDefinition();
228 $expected = ['id' => $field_definition];
229 $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
233 * Tests the getFieldDefinitions() method.
235 * @covers ::getFieldDefinitions
236 * @covers ::buildBundleFieldDefinitions
238 public function testGetFieldDefinitions() {
239 $field_definition = $this->setUpEntityWithFieldDefinition();
241 $expected = ['id' => $field_definition];
242 $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_entity_bundle'));
246 * Tests the getFieldStorageDefinitions() method.
248 * @covers ::getFieldStorageDefinitions
249 * @covers ::buildFieldStorageDefinitions
251 public function testGetFieldStorageDefinitions() {
252 $field_definition = $this->setUpEntityWithFieldDefinition(TRUE);
253 $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
254 $field_storage_definition->getName()->willReturn('field_storage');
256 $definitions = ['field_storage' => $field_storage_definition->reveal()];
258 $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]);
259 $this->moduleHandler->getImplementations('entity_field_storage_info')->willReturn(['example_module']);
260 $this->moduleHandler->invoke('example_module', 'entity_field_storage_info', [$this->entityType])->willReturn($definitions);
261 $this->moduleHandler->alter('entity_field_storage_info', $definitions, $this->entityType)->willReturn(NULL);
264 'id' => $field_definition,
265 'field_storage' => $field_storage_definition->reveal(),
267 $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
271 * Tests the getBaseFieldDefinitions() method with a translatable entity type.
273 * @covers ::getBaseFieldDefinitions
274 * @covers ::buildBaseFieldDefinitions
276 * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode
278 public function testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode($default_langcode_key) {
279 $this->setUpEntityWithFieldDefinition(FALSE, 'id', ['langcode' => 'langcode', 'default_langcode' => $default_langcode_key]);
281 $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
282 $field_definition->isTranslatable()->willReturn(TRUE);
284 $entity_class = EntityManagerTestEntity::class;
285 $entity_class::$baseFieldDefinitions += ['langcode' => $field_definition];
287 $this->entityType->isTranslatable()->willReturn(TRUE);
289 $definitions = $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
291 $this->assertTrue(isset($definitions[$default_langcode_key]));
295 * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode().
300 public function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode() {
302 ['default_langcode'],
303 ['custom_default_langcode_key'],
308 * Tests the getBaseFieldDefinitions() method with a translatable entity type.
310 * @covers ::getBaseFieldDefinitions
311 * @covers ::buildBaseFieldDefinitions
313 * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode
315 public function testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode($provide_key, $provide_field, $translatable) {
316 $keys = $provide_key ? ['langcode' => 'langcode'] : [];
317 $this->setUpEntityWithFieldDefinition(FALSE, 'id', $keys);
319 if ($provide_field) {
320 $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
321 $field_definition->isTranslatable()->willReturn($translatable);
322 if (!$translatable) {
323 $field_definition->setTranslatable(!$translatable)->shouldBeCalled();
326 $entity_class = EntityManagerTestEntity::class;
327 $entity_class::$baseFieldDefinitions += ['langcode' => $field_definition->reveal()];
330 $this->entityType->isTranslatable()->willReturn(TRUE);
331 $this->entityType->getLabel()->willReturn('Test');
333 $this->setExpectedException(\LogicException::class, 'The Test entity type cannot be translatable as it does not define a translatable "langcode" field.');
334 $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
338 * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode().
343 public function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode() {
352 * Tests the getBaseFieldDefinitions() method with caching.
354 * @covers ::getBaseFieldDefinitions
356 public function testGetBaseFieldDefinitionsWithCaching() {
357 $field_definition = $this->setUpEntityWithFieldDefinition();
359 $expected = ['id' => $field_definition];
361 $this->cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
364 $this->cacheBackend->set('entity_base_field_definitions:test_entity_type:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_field_info'])
365 ->will(function ($args) {
366 $data = (object) ['data' => $args[1]];
367 $this->get('entity_base_field_definitions:test_entity_type:en')
373 $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
374 $this->entityFieldManager->testClearEntityFieldInfo();
375 $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
379 * Tests the getFieldDefinitions() method with caching.
381 * @covers ::getFieldDefinitions
383 public function testGetFieldDefinitionsWithCaching() {
384 $field_definition = $this->setUpEntityWithFieldDefinition(FALSE, 'id');
386 $expected = ['id' => $field_definition];
388 $this->cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
389 ->willReturn((object) ['data' => $expected])
390 ->shouldBeCalledTimes(2);
391 $this->cacheBackend->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
393 ->shouldBeCalledTimes(1);
394 $this->cacheBackend->set('entity_bundle_field_definitions:test_entity_type:test_bundle:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_field_info'])
395 ->will(function ($args) {
396 $data = (object) ['data' => $args[1]];
397 $this->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
403 $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle'));
404 $this->entityFieldManager->testClearEntityFieldInfo();
405 $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle'));
409 * Tests the getFieldStorageDefinitions() method with caching.
411 * @covers ::getFieldStorageDefinitions
413 public function testGetFieldStorageDefinitionsWithCaching() {
414 $field_definition = $this->setUpEntityWithFieldDefinition(TRUE, 'id');
415 $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
416 $field_storage_definition->getName()->willReturn('field_storage');
418 $definitions = ['field_storage' => $field_storage_definition->reveal()];
420 $this->moduleHandler->getImplementations('entity_field_storage_info')->willReturn(['example_module']);
421 $this->moduleHandler->invoke('example_module', 'entity_field_storage_info', [$this->entityType])->willReturn($definitions);
422 $this->moduleHandler->alter('entity_field_storage_info', $definitions, $this->entityType)->willReturn(NULL);
425 'id' => $field_definition,
426 'field_storage' => $field_storage_definition->reveal(),
429 $this->cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
430 ->willReturn((object) ['data' => ['id' => $expected['id']]])
431 ->shouldBeCalledTimes(2);
432 $this->cacheBackend->get('entity_field_storage_definitions:test_entity_type:en')->willReturn(FALSE);
434 $this->cacheBackend->set('entity_field_storage_definitions:test_entity_type:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_field_info'])
435 ->will(function () use ($expected) {
436 $this->get('entity_field_storage_definitions:test_entity_type:en')
437 ->willReturn((object) ['data' => $expected])
442 $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
443 $this->entityFieldManager->testClearEntityFieldInfo();
444 $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
448 * Tests the getBaseFieldDefinitions() method with an invalid definition.
450 * @covers ::getBaseFieldDefinitions
451 * @covers ::buildBaseFieldDefinitions
453 public function testGetBaseFieldDefinitionsInvalidDefinition() {
454 $this->setUpEntityWithFieldDefinition(FALSE, 'langcode', ['langcode' => 'langcode']);
456 $this->entityType->isTranslatable()->willReturn(TRUE);
457 $this->entityType->getLabel()->willReturn('the_label');
459 $this->setExpectedException(\LogicException::class);
460 $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
464 * Tests that getFieldDefinitions() method sets the 'provider' definition key.
466 * @covers ::getFieldDefinitions
467 * @covers ::buildBundleFieldDefinitions
469 public function testGetFieldDefinitionsProvider() {
470 $this->setUpEntityWithFieldDefinition(TRUE);
472 $module = 'entity_manager_test_module';
474 // @todo Mock FieldDefinitionInterface once it exposes a proper provider
475 // setter. See https://www.drupal.org/node/2225961.
476 $field_definition = $this->prophesize(BaseFieldDefinition::class);
478 // We expect two calls as the field definition will be returned from both
479 // base and bundle entity field info hook implementations.
480 $field_definition->getProvider()->shouldBeCalled();
481 $field_definition->setProvider($module)->shouldBeCalledTimes(2);
482 $field_definition->setName(0)->shouldBeCalledTimes(2);
483 $field_definition->setTargetEntityTypeId('test_entity_type')->shouldBeCalled();
484 $field_definition->setTargetBundle(NULL)->shouldBeCalled();
485 $field_definition->setTargetBundle('test_bundle')->shouldBeCalled();
487 $this->moduleHandler->getImplementations(Argument::type('string'))->willReturn([$module]);
488 $this->moduleHandler->invoke($module, 'entity_base_field_info', [$this->entityType])->willReturn([$field_definition->reveal()]);
489 $this->moduleHandler->invoke($module, 'entity_bundle_field_info', Argument::type('array'))->willReturn([$field_definition->reveal()]);
491 $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle');
495 * Prepares an entity that defines a field definition.
497 * @param bool $custom_invoke_all
498 * (optional) Whether the test will set up its own
499 * ModuleHandlerInterface::invokeAll() implementation. Defaults to FALSE.
500 * @param string $field_definition_id
501 * (optional) The ID to use for the field definition. Defaults to 'id'.
502 * @param array $entity_keys
503 * (optional) An array of entity keys for the mocked entity type. Defaults
506 * @return \Drupal\Core\Field\BaseFieldDefinition|\Prophecy\Prophecy\ProphecyInterface
507 * A field definition object.
509 protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $field_definition_id = 'id', $entity_keys = []) {
510 $field_type_manager = $this->prophesize(FieldTypePluginManagerInterface::class);
511 $field_type_manager->getDefaultStorageSettings('boolean')->willReturn([]);
512 $field_type_manager->getDefaultFieldSettings('boolean')->willReturn([]);
513 $this->container->get('plugin.manager.field.field_type')->willReturn($field_type_manager->reveal());
515 $string_translation = $this->prophesize(TranslationInterface::class);
516 $this->container->get('string_translation')->willReturn($string_translation->reveal());
518 $entity_class = EntityManagerTestEntity::class;
520 $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
521 $entity_class::$baseFieldDefinitions = [
522 $field_definition_id => $field_definition->reveal(),
524 $entity_class::$bundleFieldDefinitions = [];
526 if (!$custom_invoke_all) {
527 $this->moduleHandler->getImplementations(Argument::cetera())->willReturn([]);
530 // Mock the base field definition override.
531 $override_entity_type = $this->prophesize(EntityTypeInterface::class);
533 $this->entityType = $this->prophesize(EntityTypeInterface::class);
534 $this->setUpEntityTypeDefinitions(['test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type]);
536 $storage = $this->prophesize(ConfigEntityStorageInterface::class);
537 $storage->loadMultiple(Argument::type('array'))->willReturn([]);
538 $this->entityTypeManager->getStorage('base_field_override')->willReturn($storage->reveal());
540 $this->entityType->getClass()->willReturn($entity_class);
541 $this->entityType->getKeys()->willReturn($entity_keys + ['default_langcode' => 'default_langcode']);
542 $this->entityType->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
543 $this->entityType->isTranslatable()->willReturn(FALSE);
544 $this->entityType->isRevisionable()->willReturn(FALSE);
545 $this->entityType->getProvider()->willReturn('the_provider');
546 $this->entityType->id()->willReturn('the_entity_id');
548 return $field_definition->reveal();
552 * Tests the clearCachedFieldDefinitions() method.
554 * @covers ::clearCachedFieldDefinitions
556 public function testClearCachedFieldDefinitions() {
557 $this->setUpEntityTypeDefinitions();
559 $this->cacheTagsInvalidator->invalidateTags(['entity_field_info'])->shouldBeCalled();
560 $this->container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal())->shouldBeCalled();
562 $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
564 $this->entityFieldManager->clearCachedFieldDefinitions();
568 * @covers ::getExtraFields
570 public function testGetExtraFields() {
571 $this->setUpEntityTypeDefinitions();
573 $entity_type_id = $this->randomMachineName();
574 $bundle = $this->randomMachineName();
575 $language_code = 'en';
576 $hook_bundle_extra_fields = [
580 'foo_extra_field' => [
587 $processed_hook_bundle_extra_fields = $hook_bundle_extra_fields;
588 $processed_hook_bundle_extra_fields[$entity_type_id][$bundle] += [
591 $cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $language_code;
593 $language = new Language(['id' => $language_code]);
594 $this->languageManager->getCurrentLanguage()
595 ->willReturn($language)
596 ->shouldBeCalledTimes(1);
598 $this->cacheBackend->get($cache_id)->shouldBeCalled();
600 $this->moduleHandler->invokeAll('entity_extra_field_info')->willReturn($hook_bundle_extra_fields);
601 $this->moduleHandler->alter('entity_extra_field_info', $hook_bundle_extra_fields)->shouldBeCalled();
603 $this->cacheBackend->set($cache_id, $processed_hook_bundle_extra_fields[$entity_type_id][$bundle], Cache::PERMANENT, ['entity_field_info'])->shouldBeCalled();
605 $this->assertSame($processed_hook_bundle_extra_fields[$entity_type_id][$bundle], $this->entityFieldManager->getExtraFields($entity_type_id, $bundle));
609 * @covers ::getFieldMap
611 public function testGetFieldMap() {
612 $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([])->shouldBeCalled();
614 // Set up a content entity type.
615 $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
616 $entity_class = EntityManagerTestEntity::class;
618 // Define an ID field definition as a base field.
619 $id_definition = $this->prophesize(FieldDefinitionInterface::class);
620 $id_definition->getType()->willReturn('integer');
621 $base_field_definitions = [
622 'id' => $id_definition->reveal(),
624 $entity_class::$baseFieldDefinitions = $base_field_definitions;
626 // Set up the stored bundle field map.
627 $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
628 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
629 $key_value_store->getAll()->willReturn([
630 'test_entity_type' => [
633 'bundles' => ['second_bundle' => 'second_bundle'],
638 // Set up a non-content entity type.
639 $non_content_entity_type = $this->prophesize(EntityTypeInterface::class);
641 // Mock the base field definition override.
642 $override_entity_type = $this->prophesize(EntityTypeInterface::class);
644 $this->setUpEntityTypeDefinitions([
645 'test_entity_type' => $entity_type,
646 'non_fieldable' => $non_content_entity_type,
647 'base_field_override' => $override_entity_type,
650 $entity_type->getClass()->willReturn($entity_class);
651 $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode']);
652 $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
653 $entity_type->isTranslatable()->shouldBeCalled();
654 $entity_type->isRevisionable()->shouldBeCalled();
655 $entity_type->getProvider()->shouldBeCalled();
657 $non_content_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
659 $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
661 // Set up the entity type bundle info to return two bundles for the
662 // fieldable entity type.
663 $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
664 'first_bundle' => 'first_bundle',
665 'second_bundle' => 'second_bundle',
666 ])->shouldBeCalled();
667 $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]);
670 'test_entity_type' => [
673 'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
677 'bundles' => ['second_bundle' => 'second_bundle'],
681 $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
685 * @covers ::getFieldMap
687 public function testGetFieldMapFromCache() {
689 'test_entity_type' => [
692 'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
696 'bundles' => ['second_bundle' => 'second_bundle'],
700 $this->setUpEntityTypeDefinitions();
701 $this->cacheBackend->get('entity_field_map')->willReturn((object) ['data' => $expected]);
703 // Call the field map twice to make sure the static cache works.
704 $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
705 $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
709 * @covers ::getFieldMapByFieldType
711 public function testGetFieldMapByFieldType() {
712 // Set up a content entity type.
713 $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
714 $entity_class = EntityManagerTestEntity::class;
716 // Set up the entity type bundle info to return two bundles for the
717 // fieldable entity type.
718 $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
719 'first_bundle' => 'first_bundle',
720 'second_bundle' => 'second_bundle',
721 ])->shouldBeCalled();
722 $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([])->shouldBeCalled();
724 // Define an ID field definition as a base field.
725 $id_definition = $this->prophesize(FieldDefinitionInterface::class);
726 $id_definition->getType()->willReturn('integer')->shouldBeCalled();
727 $base_field_definitions = [
728 'id' => $id_definition->reveal(),
730 $entity_class::$baseFieldDefinitions = $base_field_definitions;
732 // Set up the stored bundle field map.
733 $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
734 $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal())->shouldBeCalled();
735 $key_value_store->getAll()->willReturn([
736 'test_entity_type' => [
739 'bundles' => ['second_bundle' => 'second_bundle'],
742 ])->shouldBeCalled();
744 // Mock the base field definition override.
745 $override_entity_type = $this->prophesize(EntityTypeInterface::class);
747 $this->setUpEntityTypeDefinitions([
748 'test_entity_type' => $entity_type,
749 'base_field_override' => $override_entity_type,
752 $entity_type->getClass()->willReturn($entity_class)->shouldBeCalled();
753 $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode'])->shouldBeCalled();
754 $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE)->shouldBeCalled();
755 $entity_type->isTranslatable()->shouldBeCalled();
756 $entity_type->isRevisionable()->shouldBeCalled();
757 $entity_type->getProvider()->shouldBeCalled();
759 $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE)->shouldBeCalled();
761 $integerFields = $this->entityFieldManager->getFieldMapByFieldType('integer');
762 $this->assertCount(1, $integerFields['test_entity_type']);
763 $this->assertArrayNotHasKey('non_fieldable', $integerFields);
764 $this->assertArrayHasKey('id', $integerFields['test_entity_type']);
765 $this->assertArrayNotHasKey('by_bundle', $integerFields['test_entity_type']);
767 $stringFields = $this->entityFieldManager->getFieldMapByFieldType('string');
768 $this->assertCount(1, $stringFields['test_entity_type']);
769 $this->assertArrayNotHasKey('non_fieldable', $stringFields);
770 $this->assertArrayHasKey('by_bundle', $stringFields['test_entity_type']);
771 $this->assertArrayNotHasKey('id', $stringFields['test_entity_type']);
776 class TestEntityFieldManager extends EntityFieldManager {
779 * Allows the static caches to be cleared.
781 public function testClearEntityFieldInfo() {
782 $this->baseFieldDefinitions = [];
783 $this->fieldDefinitions = [];
784 $this->fieldStorageDefinitions = [];
790 * Provides a content entity with dummy static method implementations.
792 abstract class EntityManagerTestEntity implements \Iterator, ContentEntityInterface {
795 * The base field definitions.
797 * @var \Drupal\Core\Field\FieldDefinitionInterface[]
799 public static $baseFieldDefinitions = [];
802 * The bundle field definitions.
805 * Keys are entity type IDs, values are arrays of which the keys are bundle
806 * names and the values are field definitions.
808 public static $bundleFieldDefinitions = [];
813 public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
814 return static::$baseFieldDefinitions;
820 public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
821 return isset(static::$bundleFieldDefinitions[$entity_type->id()][$bundle]) ? static::$bundleFieldDefinitions[$entity_type->id()][$bundle] : [];