1. 8.3.x core/lib/Drupal/Core/Entity/EntityViewBuilder.php
  2. 8.0.x core/lib/Drupal/Core/Entity/EntityViewBuilder.php
  3. 8.1.x core/lib/Drupal/Core/Entity/EntityViewBuilder.php
  4. 8.2.x core/lib/Drupal/Core/Entity/EntityViewBuilder.php
  5. 8.4.x core/lib/Drupal/Core/Entity/EntityViewBuilder.php

Namespace

Drupal\Core\Entity

File

core/lib/Drupal/Core/Entity/EntityViewBuilder.php
View source
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
  5. use Drupal\Core\Entity\Entity\EntityViewDisplay;
  6. use Drupal\Core\Field\FieldItemInterface;
  7. use Drupal\Core\Field\FieldItemListInterface;
  8. use Drupal\Core\Language\LanguageManagerInterface;
  9. use Drupal\Core\Render\Element;
  10. use Drupal\Core\Theme\Registry;
  11. use Drupal\Core\TypedData\TranslatableInterface;
  12. use Symfony\Component\DependencyInjection\ContainerInterface;
  13. /**
  14. * Base class for entity view builders.
  15. *
  16. * @ingroup entity_api
  17. */
  18. class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterface, EntityViewBuilderInterface {
  19. /**
  20. * The type of entities for which this view builder is instantiated.
  21. *
  22. * @var string
  23. */
  24. protected $entityTypeId;
  25. /**
  26. * Information about the entity type.
  27. *
  28. * @var \Drupal\Core\Entity\EntityTypeInterface
  29. */
  30. protected $entityType;
  31. /**
  32. * The entity manager service.
  33. *
  34. * @var \Drupal\Core\Entity\EntityManagerInterface
  35. */
  36. protected $entityManager;
  37. /**
  38. * The cache bin used to store the render cache.
  39. *
  40. * @var string
  41. */
  42. protected $cacheBin = 'render';
  43. /**
  44. * The language manager.
  45. *
  46. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  47. */
  48. protected $languageManager;
  49. /**
  50. * The theme registry.
  51. *
  52. * @var \Drupal\Core\Theme\Registry
  53. */
  54. protected $themeRegistry;
  55. /**
  56. * The EntityViewDisplay objects created for individual field rendering.
  57. *
  58. * @see \Drupal\Core\Entity\EntityViewBuilder::getSingleFieldDisplay()
  59. *
  60. * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
  61. */
  62. protected $singleFieldDisplays;
  63. /**
  64. * Constructs a new EntityViewBuilder.
  65. *
  66. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  67. * The entity type definition.
  68. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  69. * The entity manager service.
  70. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  71. * The language manager.
  72. * @param \Drupal\Core\Theme\Registry $theme_registry
  73. * The theme registry.
  74. */
  75. public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, Registry $theme_registry = NULL) {
  76. $this->entityTypeId = $entity_type->id();
  77. $this->entityType = $entity_type;
  78. $this->entityManager = $entity_manager;
  79. $this->languageManager = $language_manager;
  80. $this->themeRegistry = $theme_registry ?: \Drupal::service('theme.registry');
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
  86. return new static(
  87. $entity_type,
  88. $container->get('entity.manager'),
  89. $container->get('language_manager'),
  90. $container->get('theme.registry')
  91. );
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
  97. $build_list = $this->viewMultiple(array($entity), $view_mode, $langcode);
  98. // The default ::buildMultiple() #pre_render callback won't run, because we
  99. // extract a child element of the default renderable array. Thus we must
  100. // assign an alternative #pre_render callback that applies the necessary
  101. // transformations and then still calls ::buildMultiple().
  102. $build = $build_list[0];
  103. $build['#pre_render'][] = array($this, 'build');
  104. return $build;
  105. }
  106. /**
  107. * {@inheritdoc}
  108. */
  109. public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
  110. $build_list = array(
  111. '#sorted' => TRUE,
  112. '#pre_render' => array(array($this, 'buildMultiple')),
  113. );
  114. $weight = 0;
  115. foreach ($entities as $key => $entity) {
  116. // Ensure that from now on we are dealing with the proper translation
  117. // object.
  118. $entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
  119. // Set build defaults.
  120. $build_list[$key] = $this->getBuildDefaults($entity, $view_mode);
  121. $entityType = $this->entityTypeId;
  122. $this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode);
  123. $build_list[$key]['#weight'] = $weight++;
  124. }
  125. return $build_list;
  126. }
  127. /**
  128. * Provides entity-specific defaults to the build process.
  129. *
  130. * @param \Drupal\Core\Entity\EntityInterface $entity
  131. * The entity for which the defaults should be provided.
  132. * @param string $view_mode
  133. * The view mode that should be used.
  134. *
  135. * @return array
  136. */
  137. protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
  138. // Allow modules to change the view mode.
  139. $context = [];
  140. $this->moduleHandler()->alter('entity_view_mode', $view_mode, $entity, $context);
  141. $build = array(
  142. "#{$this->entityTypeId}" => $entity,
  143. '#view_mode' => $view_mode,
  144. // Collect cache defaults for this entity.
  145. '#cache' => array(
  146. 'tags' => Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()),
  147. 'contexts' => $entity->getCacheContexts(),
  148. 'max-age' => $entity->getCacheMaxAge(),
  149. ),
  150. );
  151. // Add the default #theme key if a template exists for it.
  152. if ($this->themeRegistry->getRuntime()->has($this->entityTypeId)) {
  153. $build['#theme'] = $this->entityTypeId;
  154. }
  155. // Cache the rendered output if permitted by the view mode and global entity
  156. // type configuration.
  157. if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
  158. $build['#cache'] += array(
  159. 'keys' => array(
  160. 'entity_view',
  161. $this->entityTypeId,
  162. $entity->id(),
  163. $view_mode,
  164. ),
  165. 'bin' => $this->cacheBin,
  166. );
  167. if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
  168. $build['#cache']['keys'][] = $entity->language()->getId();
  169. }
  170. }
  171. return $build;
  172. }
  173. /**
  174. * Builds an entity's view; augments entity defaults.
  175. *
  176. * This function is assigned as a #pre_render callback in ::view().
  177. *
  178. * It transforms the renderable array for a single entity to the same
  179. * structure as if we were rendering multiple entities, and then calls the
  180. * default ::buildMultiple() #pre_render callback.
  181. *
  182. * @param array $build
  183. * A renderable array containing build information and context for an entity
  184. * view.
  185. *
  186. * @return array
  187. * The updated renderable array.
  188. *
  189. * @see drupal_render()
  190. */
  191. public function build(array $build) {
  192. $build_list = [$build];
  193. $build_list = $this->buildMultiple($build_list);
  194. return $build_list[0];
  195. }
  196. /**
  197. * Builds multiple entities' views; augments entity defaults.
  198. *
  199. * This function is assigned as a #pre_render callback in ::viewMultiple().
  200. *
  201. * By delaying the building of an entity until the #pre_render processing in
  202. * drupal_render(), the processing cost of assembling an entity's renderable
  203. * array is saved on cache-hit requests.
  204. *
  205. * @param array $build_list
  206. * A renderable array containing build information and context for an
  207. * entity view.
  208. *
  209. * @return array
  210. * The updated renderable array.
  211. *
  212. * @see drupal_render()
  213. */
  214. public function buildMultiple(array $build_list) {
  215. // Build the view modes and display objects.
  216. $view_modes = array();
  217. $entity_type_key = "#{$this->entityTypeId}";
  218. $view_hook = "{$this->entityTypeId}_view";
  219. // Find the keys for the ContentEntities in the build; Store entities for
  220. // rendering by view_mode.
  221. $children = Element::children($build_list);
  222. foreach ($children as $key) {
  223. if (isset($build_list[$key][$entity_type_key])) {
  224. $entity = $build_list[$key][$entity_type_key];
  225. if ($entity instanceof FieldableEntityInterface) {
  226. $view_modes[$build_list[$key]['#view_mode']][$key] = $entity;
  227. }
  228. }
  229. }
  230. // Build content for the displays represented by the entities.
  231. foreach ($view_modes as $view_mode => $view_mode_entities) {
  232. $displays = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $view_mode);
  233. $this->buildComponents($build_list, $view_mode_entities, $displays, $view_mode);
  234. foreach (array_keys($view_mode_entities) as $key) {
  235. // Allow for alterations while building, before rendering.
  236. $entity = $build_list[$key][$entity_type_key];
  237. $display = $displays[$entity->bundle()];
  238. $this->moduleHandler()->invokeAll($view_hook, [&$build_list[$key], $entity, $display, $view_mode]);
  239. $this->moduleHandler()->invokeAll('entity_view', [&$build_list[$key], $entity, $display, $view_mode]);
  240. $this->alterBuild($build_list[$key], $entity, $display, $view_mode);
  241. // Assign the weights configured in the display.
  242. // @todo: Once https://www.drupal.org/node/1875974 provides the missing
  243. // API, only do it for 'extra fields', since other components have
  244. // been taken care of in EntityViewDisplay::buildMultiple().
  245. foreach ($display->getComponents() as $name => $options) {
  246. if (isset($build_list[$key][$name])) {
  247. $build_list[$key][$name]['#weight'] = $options['weight'];
  248. }
  249. }
  250. // Allow modules to modify the render array.
  251. $this->moduleHandler()->alter(array($view_hook, 'entity_view'), $build_list[$key], $entity, $display);
  252. }
  253. }
  254. return $build_list;
  255. }
  256. /**
  257. * {@inheritdoc}
  258. */
  259. public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
  260. $entities_by_bundle = array();
  261. foreach ($entities as $id => $entity) {
  262. // Initialize the field item attributes for the fields being displayed.
  263. // The entity can include fields that are not displayed, and the display
  264. // can include components that are not fields, so we want to act on the
  265. // intersection. However, the entity can have many more fields than are
  266. // displayed, so we avoid the cost of calling $entity->getProperties()
  267. // by iterating the intersection as follows.
  268. foreach ($displays[$entity->bundle()]->getComponents() as $name => $options) {
  269. if ($entity->hasField($name)) {
  270. foreach ($entity->get($name) as $item) {
  271. $item->_attributes = array();
  272. }
  273. }
  274. }
  275. // Group the entities by bundle.
  276. $entities_by_bundle[$entity->bundle()][$id] = $entity;
  277. }
  278. // Invoke hook_entity_prepare_view().
  279. $this->moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode));
  280. // Let the displays build their render arrays.
  281. foreach ($entities_by_bundle as $bundle => $bundle_entities) {
  282. $display_build = $displays[$bundle]->buildMultiple($bundle_entities);
  283. foreach ($bundle_entities as $id => $entity) {
  284. $build[$id] += $display_build[$id];
  285. }
  286. }
  287. }
  288. /**
  289. * Specific per-entity building.
  290. *
  291. * @param array $build
  292. * The render array that is being created.
  293. * @param \Drupal\Core\Entity\EntityInterface $entity
  294. * The entity to be prepared.
  295. * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
  296. * The entity view display holding the display options configured for the
  297. * entity components.
  298. * @param string $view_mode
  299. * The view mode that should be used to prepare the entity.
  300. */
  301. protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { }
  302. /**
  303. * {@inheritdoc}
  304. */
  305. public function getCacheTags() {
  306. return array($this->entityTypeId . '_view');
  307. }
  308. /**
  309. * {@inheritdoc}
  310. */
  311. public function resetCache(array $entities = NULL) {
  312. // If no set of specific entities is provided, invalidate the entity view
  313. // builder's cache tag. This will invalidate all entities rendered by this
  314. // view builder.
  315. // Otherwise, if a set of specific entities is provided, invalidate those
  316. // specific entities only, plus their list cache tags, because any lists in
  317. // which these entities are rendered, must be invalidated as well. However,
  318. // even in this case, we might invalidate more cache items than necessary.
  319. // When we have a way to invalidate only those cache items that have both
  320. // the individual entity's cache tag and the view builder's cache tag, we'll
  321. // be able to optimize this further.
  322. if (isset($entities)) {
  323. $tags = [];
  324. foreach ($entities as $entity) {
  325. $tags = Cache::mergeTags($tags, $entity->getCacheTags());
  326. $tags = Cache::mergeTags($tags, $entity->getEntityType()->getListCacheTags());
  327. }
  328. Cache::invalidateTags($tags);
  329. }
  330. else {
  331. Cache::invalidateTags($this->getCacheTags());
  332. }
  333. }
  334. /**
  335. * Determines whether the view mode is cacheable.
  336. *
  337. * @param string $view_mode
  338. * Name of the view mode that should be rendered.
  339. *
  340. * @return bool
  341. * TRUE if the view mode can be cached, FALSE otherwise.
  342. */
  343. protected function isViewModeCacheable($view_mode) {
  344. if ($view_mode == 'default') {
  345. // The 'default' is not an actual view mode.
  346. return TRUE;
  347. }
  348. $view_modes_info = $this->entityManager->getViewModes($this->entityTypeId);
  349. return !empty($view_modes_info[$view_mode]['cache']);
  350. }
  351. /**
  352. * {@inheritdoc}
  353. */
  354. public function viewField(FieldItemListInterface $items, $display_options = array()) {
  355. $entity = $items->getEntity();
  356. $field_name = $items->getFieldDefinition()->getName();
  357. $display = $this->getSingleFieldDisplay($entity, $field_name, $display_options);
  358. $output = array();
  359. $build = $display->build($entity);
  360. if (isset($build[$field_name])) {
  361. $output = $build[$field_name];
  362. }
  363. return $output;
  364. }
  365. /**
  366. * {@inheritdoc}
  367. */
  368. public function viewFieldItem(FieldItemInterface $item, $display = array()) {
  369. $entity = $item->getEntity();
  370. $field_name = $item->getFieldDefinition()->getName();
  371. // Clone the entity since we are going to modify field values.
  372. $clone = clone $entity;
  373. // Push the item as the single value for the field, and defer to viewField()
  374. // to build the render array for the whole list.
  375. $clone->{$field_name}->setValue(array($item->getValue()));
  376. $elements = $this->viewField($clone->{$field_name}, $display);
  377. // Extract the part of the render array we need.
  378. $output = isset($elements[0]) ? $elements[0] : array();
  379. if (isset($elements['#access'])) {
  380. $output['#access'] = $elements['#access'];
  381. }
  382. return $output;
  383. }
  384. /**
  385. * Gets an EntityViewDisplay for rendering an individual field.
  386. *
  387. * @param \Drupal\Core\Entity\EntityInterface $entity
  388. * The entity.
  389. * @param string $field_name
  390. * The field name.
  391. * @param string|array $display_options
  392. * The display options passed to the viewField() method.
  393. *
  394. * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
  395. */
  396. protected function getSingleFieldDisplay($entity, $field_name, $display_options) {
  397. if (is_string($display_options)) {
  398. // View mode: use the Display configured for the view mode.
  399. $view_mode = $display_options;
  400. $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
  401. // Hide all fields except the current one.
  402. foreach (array_keys($entity->getFieldDefinitions()) as $name) {
  403. if ($name != $field_name) {
  404. $display->removeComponent($name);
  405. }
  406. }
  407. }
  408. else {
  409. // Array of custom display options: use a runtime Display for the
  410. // '_custom' view mode. Persist the displays created, to reduce the number
  411. // of objects (displays and formatter plugins) created when rendering a
  412. // series of fields individually for cases such as views tables.
  413. $entity_type_id = $entity->getEntityTypeId();
  414. $bundle = $entity->bundle();
  415. $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . hash('crc32b', serialize($display_options));
  416. if (!isset($this->singleFieldDisplays[$key])) {
  417. $this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
  418. 'targetEntityType' => $entity_type_id,
  419. 'bundle' => $bundle,
  420. 'status' => TRUE,
  421. ))->setComponent($field_name, $display_options);
  422. }
  423. $display = $this->singleFieldDisplays[$key];
  424. }
  425. return $display;
  426. }
  427. }

Classes

Namesort descending Description
EntityViewBuilder Base class for entity view builders.