Version 1
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Entity / EntityFieldManagerTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Entity\EntityFieldManagerTest.
6  */
7
8 namespace Drupal\Tests\Core\Entity;
9
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;
40
41 /**
42  * @coversDefaultClass \Drupal\Core\Entity\EntityFieldManager
43  * @group Entity
44  */
45 class EntityFieldManagerTest extends UnitTestCase {
46
47   /**
48    * The typed data manager.
49    *
50    * @var \Drupal\Core\TypedData\TypedDataManagerInterface|\Prophecy\Prophecy\ProphecyInterface
51    */
52   protected $typedDataManager;
53
54   /**
55    * The module handler.
56    *
57    * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
58    */
59   protected $moduleHandler;
60
61   /**
62    * The cache backend to use.
63    *
64    * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
65    */
66   protected $cacheBackend;
67
68   /**
69    * The cache tags invalidator.
70    *
71    * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\Prophecy\Prophecy\ProphecyInterface
72    */
73   protected $cacheTagsInvalidator;
74
75   /**
76    * The language manager.
77    *
78    * @var \Drupal\Core\Language\LanguageManagerInterface|\Prophecy\Prophecy\ProphecyInterface
79    */
80   protected $languageManager;
81
82   /**
83    * The keyvalue factory.
84    *
85    * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
86    */
87   protected $keyValueFactory;
88
89   /**
90    * The event dispatcher.
91    *
92    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\Prophecy\Prophecy\ProphecyInterface
93    */
94   protected $eventDispatcher;
95
96   /**
97    * The entity type manager.
98    *
99    * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
100    */
101   protected $entityTypeManager;
102
103   /**
104    * The entity type repository.
105    *
106    * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
107    */
108   protected $entityTypeRepository;
109
110   /**
111    * The entity type bundle info.
112    *
113    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
114    */
115   protected $entityTypeBundleInfo;
116
117   /**
118    * The entity display repository.
119    *
120    * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
121    */
122   protected $entityDisplayRepository;
123
124   /**
125    * The entity field manager under test.
126    *
127    * @var \Drupal\Core\Entity\EntityFieldManager
128    */
129   protected $entityFieldManager;
130
131   /**
132    * The dependency injection container.
133    *
134    * @var \Symfony\Component\DependencyInjection\ContainerInterface|\Prophecy\Prophecy\ProphecyInterface
135    */
136   protected $container;
137
138   /**
139    * The entity type definition.
140    *
141    * @var \Drupal\Core\Entity\EntityTypeInterface|\Prophecy\Prophecy\ProphecyInterface
142    */
143   protected $entityType;
144
145   /**
146    * {@inheritdoc}
147    */
148   protected function setUp() {
149     parent::setUp();
150
151     $this->container = $this->prophesize(ContainerInterface::class);
152     \Drupal::setContainer($this->container->reveal());
153
154     $this->typedDataManager = $this->prophesize(TypedDataManagerInterface::class);
155     $this->typedDataManager->getDefinition('field_item:boolean')->willReturn([
156       'class' => BooleanItem::class,
157     ]);
158     $this->container->get('typed_data_manager')->willReturn($this->typedDataManager->reveal());
159
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);
163
164     $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
165     $this->cacheTagsInvalidator = $this->prophesize(CacheTagsInvalidatorInterface::class);
166
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']]);
171
172     $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class);
173
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);
178
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());
180   }
181
182   /**
183    * Sets up the entity type manager to be tested.
184    *
185    * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
186    *   (optional) An array of entity type definitions.
187    */
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
193       // always be mocked.
194       $entity_type->getLinkTemplates()->willReturn([]);
195
196       // Give the entity type a legitimate class to return.
197       $entity_type->getClass()->willReturn($class);
198
199       $definitions[$key] = $entity_type->reveal();
200     }
201
202     $this->entityTypeManager->getDefinition(Argument::type('string'))
203       ->will(function ($args) use ($definitions) {
204         if (isset($definitions[$args[0]])) {
205           return $definitions[$args[0]];
206         }
207         throw new PluginNotFoundException($args[0]);
208       });
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]];
213         }
214       });
215     $this->entityTypeManager->getDefinitions()->willReturn($definitions);
216
217   }
218
219   /**
220    * Tests the getBaseFieldDefinitions() method.
221    *
222    * @covers ::getBaseFieldDefinitions
223    * @covers ::buildBaseFieldDefinitions
224    */
225   public function testGetBaseFieldDefinitions() {
226     $field_definition = $this->setUpEntityWithFieldDefinition();
227
228     $expected = ['id' => $field_definition];
229     $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
230   }
231
232   /**
233    * Tests the getFieldDefinitions() method.
234    *
235    * @covers ::getFieldDefinitions
236    * @covers ::buildBundleFieldDefinitions
237    */
238   public function testGetFieldDefinitions() {
239     $field_definition = $this->setUpEntityWithFieldDefinition();
240
241     $expected = ['id' => $field_definition];
242     $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_entity_bundle'));
243   }
244
245   /**
246    * Tests the getFieldStorageDefinitions() method.
247    *
248    * @covers ::getFieldStorageDefinitions
249    * @covers ::buildFieldStorageDefinitions
250    */
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');
255
256     $definitions = ['field_storage' => $field_storage_definition->reveal()];
257
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);
262
263     $expected = [
264       'id' => $field_definition,
265       'field_storage' => $field_storage_definition->reveal(),
266     ];
267     $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
268   }
269
270   /**
271    * Tests the getBaseFieldDefinitions() method with a translatable entity type.
272    *
273    * @covers ::getBaseFieldDefinitions
274    * @covers ::buildBaseFieldDefinitions
275    *
276    * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode
277    */
278   public function testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode($default_langcode_key) {
279     $this->setUpEntityWithFieldDefinition(FALSE, 'id', ['langcode' => 'langcode', 'default_langcode' => $default_langcode_key]);
280
281     $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
282     $field_definition->isTranslatable()->willReturn(TRUE);
283
284     $entity_class = EntityManagerTestEntity::class;
285     $entity_class::$baseFieldDefinitions += ['langcode' => $field_definition];
286
287     $this->entityType->isTranslatable()->willReturn(TRUE);
288
289     $definitions = $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
290
291     $this->assertTrue(isset($definitions[$default_langcode_key]));
292   }
293
294   /**
295    * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode().
296    *
297    * @return array
298    *   Test data.
299    */
300   public function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeDefaultLangcode() {
301     return [
302       ['default_langcode'],
303       ['custom_default_langcode_key'],
304     ];
305   }
306
307   /**
308    * Tests the getBaseFieldDefinitions() method with a translatable entity type.
309    *
310    * @covers ::getBaseFieldDefinitions
311    * @covers ::buildBaseFieldDefinitions
312    *
313    * @dataProvider providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode
314    */
315   public function testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode($provide_key, $provide_field, $translatable) {
316     $keys = $provide_key ? ['langcode' => 'langcode'] : [];
317     $this->setUpEntityWithFieldDefinition(FALSE, 'id', $keys);
318
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();
324       }
325
326       $entity_class = EntityManagerTestEntity::class;
327       $entity_class::$baseFieldDefinitions += ['langcode' => $field_definition->reveal()];
328     }
329
330     $this->entityType->isTranslatable()->willReturn(TRUE);
331     $this->entityType->getLabel()->willReturn('Test');
332
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');
335   }
336
337   /**
338    * Provides test data for testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode().
339    *
340    * @return array
341    *   Test data.
342    */
343   public function providerTestGetBaseFieldDefinitionsTranslatableEntityTypeLangcode() {
344     return [
345       [FALSE, TRUE, TRUE],
346       [TRUE, FALSE, TRUE],
347       [TRUE, TRUE, FALSE],
348     ];
349   }
350
351   /**
352    * Tests the getBaseFieldDefinitions() method with caching.
353    *
354    * @covers ::getBaseFieldDefinitions
355    */
356   public function testGetBaseFieldDefinitionsWithCaching() {
357     $field_definition = $this->setUpEntityWithFieldDefinition();
358
359     $expected = ['id' => $field_definition];
360
361     $this->cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
362       ->willReturn(FALSE)
363       ->shouldBeCalled();
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')
368           ->willReturn($data)
369           ->shouldBeCalled();
370       })
371       ->shouldBeCalled();
372
373     $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
374     $this->entityFieldManager->testClearEntityFieldInfo();
375     $this->assertSame($expected, $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type'));
376   }
377
378   /**
379    * Tests the getFieldDefinitions() method with caching.
380    *
381    * @covers ::getFieldDefinitions
382    */
383   public function testGetFieldDefinitionsWithCaching() {
384     $field_definition = $this->setUpEntityWithFieldDefinition(FALSE, 'id');
385
386     $expected = ['id' => $field_definition];
387
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')
392       ->willReturn(FALSE)
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')
398           ->willReturn($data)
399           ->shouldBeCalled();
400       })
401       ->shouldBeCalled();
402
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'));
406   }
407
408   /**
409    * Tests the getFieldStorageDefinitions() method with caching.
410    *
411    * @covers ::getFieldStorageDefinitions
412    */
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');
417
418     $definitions = ['field_storage' => $field_storage_definition->reveal()];
419
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);
423
424     $expected = [
425       'id' => $field_definition,
426       'field_storage' => $field_storage_definition->reveal(),
427     ];
428
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);
433
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])
438           ->shouldBeCalled();
439       })
440       ->shouldBeCalled();
441
442
443     $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
444     $this->entityFieldManager->testClearEntityFieldInfo();
445     $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
446   }
447
448   /**
449    * Tests the getBaseFieldDefinitions() method with an invalid definition.
450    *
451    * @covers ::getBaseFieldDefinitions
452    * @covers ::buildBaseFieldDefinitions
453    */
454   public function testGetBaseFieldDefinitionsInvalidDefinition() {
455     $this->setUpEntityWithFieldDefinition(FALSE, 'langcode', ['langcode' => 'langcode']);
456
457     $this->entityType->isTranslatable()->willReturn(TRUE);
458     $this->entityType->getLabel()->willReturn('the_label');
459
460     $this->setExpectedException(\LogicException::class);
461     $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
462   }
463
464   /**
465    * Tests that getFieldDefinitions() method sets the 'provider' definition key.
466    *
467    * @covers ::getFieldDefinitions
468    * @covers ::buildBundleFieldDefinitions
469    */
470   public function testGetFieldDefinitionsProvider() {
471     $this->setUpEntityWithFieldDefinition(TRUE);
472
473     $module = 'entity_manager_test_module';
474
475     // @todo Mock FieldDefinitionInterface once it exposes a proper provider
476     //   setter. See https://www.drupal.org/node/2225961.
477     $field_definition = $this->prophesize(BaseFieldDefinition::class);
478
479     // We expect two calls as the field definition will be returned from both
480     // base and bundle entity field info hook implementations.
481     $field_definition->getProvider()->shouldBeCalled();
482     $field_definition->setProvider($module)->shouldBeCalledTimes(2);
483     $field_definition->setName(0)->shouldBeCalledTimes(2);
484     $field_definition->setTargetEntityTypeId('test_entity_type')->shouldBeCalled();
485     $field_definition->setTargetBundle(NULL)->shouldBeCalled();
486     $field_definition->setTargetBundle('test_bundle')->shouldBeCalled();
487
488     $this->moduleHandler->getImplementations(Argument::type('string'))->willReturn([$module]);
489     $this->moduleHandler->invoke($module, 'entity_base_field_info', [$this->entityType])->willReturn([$field_definition->reveal()]);
490     $this->moduleHandler->invoke($module, 'entity_bundle_field_info', Argument::type('array'))->willReturn([$field_definition->reveal()]);
491
492     $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle');
493   }
494
495   /**
496    * Prepares an entity that defines a field definition.
497    *
498    * @param bool $custom_invoke_all
499    *   (optional) Whether the test will set up its own
500    *   ModuleHandlerInterface::invokeAll() implementation. Defaults to FALSE.
501    * @param string $field_definition_id
502    *   (optional) The ID to use for the field definition. Defaults to 'id'.
503    * @param array $entity_keys
504    *   (optional) An array of entity keys for the mocked entity type. Defaults
505    *   to an empty array.
506    *
507    * @return \Drupal\Core\Field\BaseFieldDefinition|\Prophecy\Prophecy\ProphecyInterface
508    *   A field definition object.
509    */
510   protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $field_definition_id = 'id', $entity_keys = []) {
511     $field_type_manager = $this->prophesize(FieldTypePluginManagerInterface::class);
512     $field_type_manager->getDefaultStorageSettings('boolean')->willReturn([]);
513     $field_type_manager->getDefaultFieldSettings('boolean')->willReturn([]);
514     $this->container->get('plugin.manager.field.field_type')->willReturn($field_type_manager->reveal());
515
516     $string_translation = $this->prophesize(TranslationInterface::class);
517     $this->container->get('string_translation')->willReturn($string_translation->reveal());
518
519     $entity_class = EntityManagerTestEntity::class;
520
521     $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
522     $entity_class::$baseFieldDefinitions = [
523       $field_definition_id => $field_definition->reveal(),
524     ];
525     $entity_class::$bundleFieldDefinitions = [];
526
527     if (!$custom_invoke_all) {
528       $this->moduleHandler->getImplementations(Argument::cetera())->willReturn([]);
529     }
530
531     // Mock the base field definition override.
532     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
533
534     $this->entityType = $this->prophesize(EntityTypeInterface::class);
535     $this->setUpEntityTypeDefinitions(['test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type]);
536
537     $storage = $this->prophesize(ConfigEntityStorageInterface::class);
538     $storage->loadMultiple(Argument::type('array'))->willReturn([]);
539     $this->entityTypeManager->getStorage('base_field_override')->willReturn($storage->reveal());
540
541     $this->entityType->getClass()->willReturn($entity_class);
542     $this->entityType->getKeys()->willReturn($entity_keys + ['default_langcode' => 'default_langcode']);
543     $this->entityType->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
544     $this->entityType->isTranslatable()->willReturn(FALSE);
545     $this->entityType->getProvider()->willReturn('the_provider');
546     $this->entityType->id()->willReturn('the_entity_id');
547
548     return $field_definition->reveal();
549   }
550
551   /**
552    * Tests the clearCachedFieldDefinitions() method.
553    *
554    * @covers ::clearCachedFieldDefinitions
555    */
556   public function testClearCachedFieldDefinitions() {
557     $this->setUpEntityTypeDefinitions();
558
559     $this->cacheTagsInvalidator->invalidateTags(['entity_field_info'])->shouldBeCalled();
560     $this->container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal())->shouldBeCalled();
561
562     $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
563
564     $this->entityFieldManager->clearCachedFieldDefinitions();
565   }
566
567   /**
568    * @covers ::getExtraFields
569    */
570   public function testGetExtraFields() {
571     $this->setUpEntityTypeDefinitions();
572
573     $entity_type_id = $this->randomMachineName();
574     $bundle = $this->randomMachineName();
575     $language_code = 'en';
576     $hook_bundle_extra_fields = [
577       $entity_type_id => [
578         $bundle => [
579           'form' => [
580             'foo_extra_field' => [
581               'label' => 'Foo',
582             ],
583           ],
584         ],
585       ],
586     ];
587     $processed_hook_bundle_extra_fields = $hook_bundle_extra_fields;
588     $processed_hook_bundle_extra_fields[$entity_type_id][$bundle] += [
589       'display' => [],
590     ];
591     $cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $language_code;
592
593     $language = new Language(['id' => $language_code]);
594     $this->languageManager->getCurrentLanguage()
595       ->willReturn($language)
596       ->shouldBeCalledTimes(1);
597
598     $this->cacheBackend->get($cache_id)->shouldBeCalled();
599
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();
602
603     $this->cacheBackend->set($cache_id, $processed_hook_bundle_extra_fields[$entity_type_id][$bundle], Cache::PERMANENT, ['entity_field_info'])->shouldBeCalled();
604
605     $this->assertSame($processed_hook_bundle_extra_fields[$entity_type_id][$bundle], $this->entityFieldManager->getExtraFields($entity_type_id, $bundle));
606   }
607
608   /**
609    * @covers ::getFieldMap
610    */
611   public function testGetFieldMap() {
612     $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([])->shouldBeCalled();
613
614     // Set up a content entity type.
615     $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
616     $entity_class = EntityManagerTestEntity::class;
617
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(),
623     ];
624     $entity_class::$baseFieldDefinitions = $base_field_definitions;
625
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' => [
631         'by_bundle' => [
632           'type' => 'string',
633           'bundles' => ['second_bundle' => 'second_bundle'],
634         ],
635       ],
636     ]);
637
638     // Set up a non-content entity type.
639     $non_content_entity_type = $this->prophesize(EntityTypeInterface::class);
640
641     // Mock the base field definition override.
642     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
643
644     $this->setUpEntityTypeDefinitions([
645       'test_entity_type' => $entity_type,
646       'non_fieldable' => $non_content_entity_type,
647       'base_field_override' => $override_entity_type,
648     ]);
649
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->getProvider()->shouldBeCalled();
655
656     $non_content_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
657
658     $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
659
660     // Set up the entity type bundle info to return two bundles for the
661     // fieldable entity type.
662     $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
663       'first_bundle' => 'first_bundle',
664       'second_bundle' => 'second_bundle',
665     ])->shouldBeCalled();
666     $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]);
667
668     $expected = [
669       'test_entity_type' => [
670         'id' => [
671           'type' => 'integer',
672           'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
673         ],
674         'by_bundle' => [
675           'type' => 'string',
676           'bundles' => ['second_bundle' => 'second_bundle'],
677         ],
678       ]
679     ];
680     $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
681   }
682
683   /**
684    * @covers ::getFieldMap
685    */
686   public function testGetFieldMapFromCache() {
687     $expected = [
688       'test_entity_type' => [
689         'id' => [
690           'type' => 'integer',
691           'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
692         ],
693         'by_bundle' => [
694           'type' => 'string',
695           'bundles' => ['second_bundle' => 'second_bundle'],
696         ],
697       ]
698     ];
699     $this->setUpEntityTypeDefinitions();
700     $this->cacheBackend->get('entity_field_map')->willReturn((object) ['data' => $expected]);
701
702     // Call the field map twice to make sure the static cache works.
703     $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
704     $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
705   }
706
707   /**
708    * @covers ::getFieldMapByFieldType
709    */
710   public function testGetFieldMapByFieldType() {
711     // Set up a content entity type.
712     $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
713     $entity_class = EntityManagerTestEntity::class;
714
715     // Set up the entity type bundle info to return two bundles for the
716     // fieldable entity type.
717     $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
718       'first_bundle' => 'first_bundle',
719       'second_bundle' => 'second_bundle',
720     ])->shouldBeCalled();
721     $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([])->shouldBeCalled();
722
723     // Define an ID field definition as a base field.
724     $id_definition = $this->prophesize(FieldDefinitionInterface::class);
725     $id_definition->getType()->willReturn('integer')->shouldBeCalled();
726     $base_field_definitions = [
727       'id' => $id_definition->reveal(),
728     ];
729     $entity_class::$baseFieldDefinitions = $base_field_definitions;
730
731     // Set up the stored bundle field map.
732     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
733     $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal())->shouldBeCalled();
734     $key_value_store->getAll()->willReturn([
735       'test_entity_type' => [
736         'by_bundle' => [
737           'type' => 'string',
738           'bundles' => ['second_bundle' => 'second_bundle'],
739         ],
740       ],
741     ])->shouldBeCalled();
742
743     // Mock the base field definition override.
744     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
745
746     $this->setUpEntityTypeDefinitions([
747       'test_entity_type' => $entity_type,
748       'base_field_override' => $override_entity_type,
749     ]);
750
751     $entity_type->getClass()->willReturn($entity_class)->shouldBeCalled();
752     $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode'])->shouldBeCalled();
753     $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE)->shouldBeCalled();
754     $entity_type->isTranslatable()->shouldBeCalled();
755     $entity_type->getProvider()->shouldBeCalled();
756
757     $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE)->shouldBeCalled();
758
759     $integerFields = $this->entityFieldManager->getFieldMapByFieldType('integer');
760     $this->assertCount(1, $integerFields['test_entity_type']);
761     $this->assertArrayNotHasKey('non_fieldable', $integerFields);
762     $this->assertArrayHasKey('id', $integerFields['test_entity_type']);
763     $this->assertArrayNotHasKey('by_bundle', $integerFields['test_entity_type']);
764
765     $stringFields = $this->entityFieldManager->getFieldMapByFieldType('string');
766     $this->assertCount(1, $stringFields['test_entity_type']);
767     $this->assertArrayNotHasKey('non_fieldable', $stringFields);
768     $this->assertArrayHasKey('by_bundle', $stringFields['test_entity_type']);
769     $this->assertArrayNotHasKey('id', $stringFields['test_entity_type']);
770   }
771
772 }
773
774 class TestEntityFieldManager extends EntityFieldManager {
775
776   /**
777    * Allows the static caches to be cleared.
778    */
779   public function testClearEntityFieldInfo() {
780     $this->baseFieldDefinitions = [];
781     $this->fieldDefinitions = [];
782     $this->fieldStorageDefinitions = [];
783   }
784
785 }
786
787 /**
788  * Provides a content entity with dummy static method implementations.
789  */
790 abstract class EntityManagerTestEntity implements \Iterator, ContentEntityInterface {
791
792   /**
793    * The base field definitions.
794    *
795    * @var \Drupal\Core\Field\FieldDefinitionInterface[]
796    */
797   public static $baseFieldDefinitions = [];
798
799   /**
800    * The bundle field definitions.
801    *
802    * @var array[]
803    *   Keys are entity type IDs, values are arrays of which the keys are bundle
804    *   names and the values are field definitions.
805    */
806   public static $bundleFieldDefinitions = [];
807
808   /**
809    * {@inheritdoc}
810    */
811   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
812     return static::$baseFieldDefinitions;
813   }
814
815   /**
816    * {@inheritdoc}
817    */
818   public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
819     return isset(static::$bundleFieldDefinitions[$entity_type->id()][$bundle]) ? static::$bundleFieldDefinitions[$entity_type->id()][$bundle] : [];
820   }
821
822 }