87b8a68fc1ccaf454afbce4913eea9f604865022
[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     $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
443     $this->entityFieldManager->testClearEntityFieldInfo();
444     $this->assertSame($expected, $this->entityFieldManager->getFieldStorageDefinitions('test_entity_type'));
445   }
446
447   /**
448    * Tests the getBaseFieldDefinitions() method with an invalid definition.
449    *
450    * @covers ::getBaseFieldDefinitions
451    * @covers ::buildBaseFieldDefinitions
452    */
453   public function testGetBaseFieldDefinitionsInvalidDefinition() {
454     $this->setUpEntityWithFieldDefinition(FALSE, 'langcode', ['langcode' => 'langcode']);
455
456     $this->entityType->isTranslatable()->willReturn(TRUE);
457     $this->entityType->getLabel()->willReturn('the_label');
458
459     $this->setExpectedException(\LogicException::class);
460     $this->entityFieldManager->getBaseFieldDefinitions('test_entity_type');
461   }
462
463   /**
464    * Tests that getFieldDefinitions() method sets the 'provider' definition key.
465    *
466    * @covers ::getFieldDefinitions
467    * @covers ::buildBundleFieldDefinitions
468    */
469   public function testGetFieldDefinitionsProvider() {
470     $this->setUpEntityWithFieldDefinition(TRUE);
471
472     $module = 'entity_manager_test_module';
473
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);
477
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();
486
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()]);
490
491     $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle');
492   }
493
494   /**
495    * Prepares an entity that defines a field definition.
496    *
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
504    *   to an empty array.
505    *
506    * @return \Drupal\Core\Field\BaseFieldDefinition|\Prophecy\Prophecy\ProphecyInterface
507    *   A field definition object.
508    */
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());
514
515     $string_translation = $this->prophesize(TranslationInterface::class);
516     $this->container->get('string_translation')->willReturn($string_translation->reveal());
517
518     $entity_class = EntityManagerTestEntity::class;
519
520     $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class);
521     $entity_class::$baseFieldDefinitions = [
522       $field_definition_id => $field_definition->reveal(),
523     ];
524     $entity_class::$bundleFieldDefinitions = [];
525
526     if (!$custom_invoke_all) {
527       $this->moduleHandler->getImplementations(Argument::cetera())->willReturn([]);
528     }
529
530     // Mock the base field definition override.
531     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
532
533     $this->entityType = $this->prophesize(EntityTypeInterface::class);
534     $this->setUpEntityTypeDefinitions(['test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type]);
535
536     $storage = $this->prophesize(ConfigEntityStorageInterface::class);
537     $storage->loadMultiple(Argument::type('array'))->willReturn([]);
538     $this->entityTypeManager->getStorage('base_field_override')->willReturn($storage->reveal());
539
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');
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->isRevisionable()->shouldBeCalled();
655     $entity_type->getProvider()->shouldBeCalled();
656
657     $non_content_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
658
659     $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
660
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([]);
668
669     $expected = [
670       'test_entity_type' => [
671         'id' => [
672           'type' => 'integer',
673           'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
674         ],
675         'by_bundle' => [
676           'type' => 'string',
677           'bundles' => ['second_bundle' => 'second_bundle'],
678         ],
679       ],
680     ];
681     $this->assertEquals($expected, $this->entityFieldManager->getFieldMap());
682   }
683
684   /**
685    * @covers ::getFieldMap
686    */
687   public function testGetFieldMapFromCache() {
688     $expected = [
689       'test_entity_type' => [
690         'id' => [
691           'type' => 'integer',
692           'bundles' => ['first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'],
693         ],
694         'by_bundle' => [
695           'type' => 'string',
696           'bundles' => ['second_bundle' => 'second_bundle'],
697         ],
698       ],
699     ];
700     $this->setUpEntityTypeDefinitions();
701     $this->cacheBackend->get('entity_field_map')->willReturn((object) ['data' => $expected]);
702
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());
706   }
707
708   /**
709    * @covers ::getFieldMapByFieldType
710    */
711   public function testGetFieldMapByFieldType() {
712     // Set up a content entity type.
713     $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
714     $entity_class = EntityManagerTestEntity::class;
715
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();
723
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(),
729     ];
730     $entity_class::$baseFieldDefinitions = $base_field_definitions;
731
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' => [
737         'by_bundle' => [
738           'type' => 'string',
739           'bundles' => ['second_bundle' => 'second_bundle'],
740         ],
741       ],
742     ])->shouldBeCalled();
743
744     // Mock the base field definition override.
745     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
746
747     $this->setUpEntityTypeDefinitions([
748       'test_entity_type' => $entity_type,
749       'base_field_override' => $override_entity_type,
750     ]);
751
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();
758
759     $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE)->shouldBeCalled();
760
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']);
766
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']);
772   }
773
774 }
775
776 class TestEntityFieldManager extends EntityFieldManager {
777
778   /**
779    * Allows the static caches to be cleared.
780    */
781   public function testClearEntityFieldInfo() {
782     $this->baseFieldDefinitions = [];
783     $this->fieldDefinitions = [];
784     $this->fieldStorageDefinitions = [];
785   }
786
787 }
788
789 /**
790  * Provides a content entity with dummy static method implementations.
791  */
792 abstract class EntityManagerTestEntity implements \Iterator, ContentEntityInterface {
793
794   /**
795    * The base field definitions.
796    *
797    * @var \Drupal\Core\Field\FieldDefinitionInterface[]
798    */
799   public static $baseFieldDefinitions = [];
800
801   /**
802    * The bundle field definitions.
803    *
804    * @var array[]
805    *   Keys are entity type IDs, values are arrays of which the keys are bundle
806    *   names and the values are field definitions.
807    */
808   public static $bundleFieldDefinitions = [];
809
810   /**
811    * {@inheritdoc}
812    */
813   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
814     return static::$baseFieldDefinitions;
815   }
816
817   /**
818    * {@inheritdoc}
819    */
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] : [];
822   }
823
824 }