EntityViewBuilderTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. namespace Drupal\KernelTests\Core\Entity;
  3. use Drupal\Core\Entity\EntityViewBuilder;
  4. use Drupal\Core\Language\LanguageInterface;
  5. use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
  6. use Drupal\Core\Cache\Cache;
  7. use Drupal\user\Entity\Role;
  8. use Drupal\user\RoleInterface;
  9. /**
  10. * Tests the entity view builder.
  11. *
  12. * @group Entity
  13. */
  14. class EntityViewBuilderTest extends EntityKernelTestBase {
  15. use EntityReferenceTestTrait;
  16. /**
  17. * {@inheritdoc}
  18. */
  19. protected function setUp() {
  20. parent::setUp();
  21. $this->installConfig(['user', 'entity_test']);
  22. // Give anonymous users permission to view test entities.
  23. Role::load(RoleInterface::ANONYMOUS_ID)
  24. ->grantPermission('view test entity')
  25. ->save();
  26. }
  27. /**
  28. * Tests entity render cache handling.
  29. */
  30. public function testEntityViewBuilderCache() {
  31. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  32. $renderer = $this->container->get('renderer');
  33. $cache_contexts_manager = \Drupal::service("cache_contexts_manager");
  34. $cache = \Drupal::cache();
  35. // Force a request via GET so we can get drupal_render() cache working.
  36. $request = \Drupal::request();
  37. $request_method = $request->server->get('REQUEST_METHOD');
  38. $request->setMethod('GET');
  39. $entity_test = $this->createTestEntity('entity_test');
  40. // Test that new entities (before they are saved for the first time) do not
  41. // generate a cache entry.
  42. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
  43. $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.');
  44. // Get a fully built entity view render array.
  45. $entity_test->save();
  46. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
  47. $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
  48. $cid = implode(':', $cid_parts);
  49. $bin = $build['#cache']['bin'];
  50. // Mock the build array to not require the theme registry.
  51. unset($build['#theme']);
  52. $build['#markup'] = 'entity_render_test';
  53. // Test that a cache entry is created.
  54. $renderer->renderRoot($build);
  55. $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
  56. // Re-save the entity and check that the cache entry has been deleted.
  57. $cache->set('kittens', 'Kitten data', Cache::PERMANENT, $build['#cache']['tags']);
  58. $entity_test->save();
  59. $this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was saved.');
  60. $this->assertFalse($cache->get('kittens'), 'The entity saving has invalidated cache tags.');
  61. // Rebuild the render array (creating a new cache entry in the process) and
  62. // delete the entity to check the cache entry is deleted.
  63. unset($build['#printed']);
  64. $renderer->renderRoot($build);
  65. $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
  66. $entity_test->delete();
  67. $this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.');
  68. // Restore the previous request method.
  69. $request->setMethod($request_method);
  70. }
  71. /**
  72. * Tests entity render cache with references.
  73. */
  74. public function testEntityViewBuilderCacheWithReferences() {
  75. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  76. $renderer = $this->container->get('renderer');
  77. $cache_contexts_manager = \Drupal::service("cache_contexts_manager");
  78. // Force a request via GET so we can get drupal_render() cache working.
  79. $request = \Drupal::request();
  80. $request_method = $request->server->get('REQUEST_METHOD');
  81. $request->setMethod('GET');
  82. // Create an entity reference field and an entity that will be referenced.
  83. $this->createEntityReferenceField('entity_test', 'entity_test', 'reference_field', 'Reference', 'entity_test');
  84. entity_get_display('entity_test', 'entity_test', 'full')->setComponent('reference_field', [
  85. 'type' => 'entity_reference_entity_view',
  86. 'settings' => ['link' => FALSE],
  87. ])->save();
  88. $entity_test_reference = $this->createTestEntity('entity_test');
  89. $entity_test_reference->save();
  90. // Get a fully built entity view render array for the referenced entity.
  91. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
  92. $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
  93. $cid_reference = implode(':', $cid_parts);
  94. $bin_reference = $build['#cache']['bin'];
  95. // Mock the build array to not require the theme registry.
  96. unset($build['#theme']);
  97. $build['#markup'] = 'entity_render_test';
  98. $renderer->renderRoot($build);
  99. // Test that a cache entry was created for the referenced entity.
  100. $this->assertTrue($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render element for the referenced entity has been cached.');
  101. // Create another entity that references the first one.
  102. $entity_test = $this->createTestEntity('entity_test');
  103. $entity_test->reference_field->entity = $entity_test_reference;
  104. $entity_test->save();
  105. // Get a fully built entity view render array.
  106. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
  107. $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
  108. $cid = implode(':', $cid_parts);
  109. $bin = $build['#cache']['bin'];
  110. // Mock the build array to not require the theme registry.
  111. unset($build['#theme']);
  112. $build['#markup'] = 'entity_render_test';
  113. $renderer->renderRoot($build);
  114. // Test that a cache entry is created.
  115. $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
  116. // Save the entity and verify that both cache entries have been deleted.
  117. $entity_test_reference->save();
  118. $this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.');
  119. $this->assertFalse($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render cache for the referenced entity has been cleared when the entity was deleted.');
  120. // Restore the previous request method.
  121. $request->setMethod($request_method);
  122. }
  123. /**
  124. * Tests entity render cache toggling.
  125. */
  126. public function testEntityViewBuilderCacheToggling() {
  127. $entity_test = $this->createTestEntity('entity_test');
  128. $entity_test->save();
  129. // Test a view mode in default conditions: render caching is enabled for
  130. // the entity type and the view mode.
  131. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
  132. $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age', 'keys', 'bin'], 'A view mode with render cache enabled has the correct output (cache tags, keys, contexts, max-age and bin).');
  133. // Test that a view mode can opt out of render caching.
  134. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
  135. $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'A view mode with render cache disabled has the correct output (only cache tags, contexts and max-age).');
  136. // Test that an entity type can opt out of render caching completely.
  137. $this->installEntitySchema('entity_test_label');
  138. $entity_test_no_cache = $this->createTestEntity('entity_test_label');
  139. $entity_test_no_cache->save();
  140. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full');
  141. $this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags, contexts and max-age set.');
  142. }
  143. /**
  144. * Tests weighting of display components.
  145. */
  146. public function testEntityViewBuilderWeight() {
  147. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  148. $renderer = $this->container->get('renderer');
  149. // Set a weight for the label component.
  150. entity_get_display('entity_test', 'entity_test', 'full')
  151. ->setComponent('label', ['weight' => 20])
  152. ->save();
  153. // Create and build a test entity.
  154. $entity_test = $this->createTestEntity('entity_test');
  155. $view = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
  156. $renderer->renderRoot($view);
  157. // Check that the weight is respected.
  158. $this->assertEqual($view['label']['#weight'], 20, 'The weight of a display component is respected.');
  159. }
  160. /**
  161. * Creates an entity for testing.
  162. *
  163. * @param string $entity_type
  164. * The entity type.
  165. *
  166. * @return \Drupal\Core\Entity\EntityInterface
  167. * The created entity.
  168. */
  169. protected function createTestEntity($entity_type) {
  170. $data = [
  171. 'bundle' => $entity_type,
  172. 'name' => $this->randomMachineName(),
  173. ];
  174. return $this->container->get('entity.manager')->getStorage($entity_type)->create($data);
  175. }
  176. /**
  177. * Tests that viewing an entity without template does not specify #theme.
  178. */
  179. public function testNoTemplate() {
  180. // Ensure that an entity type without explicit view builder uses the
  181. // default.
  182. $entity_type_manager = \Drupal::entityTypeManager();
  183. $entity_type = $entity_type_manager->getDefinition('entity_test_base_field_display');
  184. $this->assertTrue($entity_type->hasViewBuilderClass());
  185. $this->assertEquals(EntityViewBuilder::class, $entity_type->getViewBuilderClass());
  186. // Ensure that an entity without matching template does not have a #theme
  187. // key.
  188. $entity = $this->createTestEntity('entity_test');
  189. $build = $entity_type_manager->getViewBuilder('entity_test')->view($entity);
  190. $this->assertEquals($entity, $build['#entity_test']);
  191. $this->assertFalse(array_key_exists('#theme', $build));
  192. }
  193. }