3 namespace Drupal\Core\Entity\Entity;
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
7 use Drupal\Core\Entity\EntityDisplayPluginCollection;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Entity\FieldableEntityInterface;
10 use Drupal\Core\Entity\EntityDisplayBase;
11 use Drupal\Core\TypedData\TranslatableInterface;
14 * Configuration entity that contains display options for all components of a
15 * rendered entity in a given view mode.
18 * id = "entity_view_display",
19 * label = @Translation("Entity view display"),
34 class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayInterface {
39 protected $displayContext = 'view';
42 * Returns the display objects used to render a set of entities.
44 * Depending on the configuration of the view mode for each bundle, this can
45 * be either the display object associated with the view mode, or the
48 * This method should only be used internally when rendering an entity. When
49 * assigning suggested display options for a component in a given view mode,
50 * entity_get_display() should be used instead, in order to avoid
51 * inadvertently modifying the output of other view modes that might happen to
52 * use the 'default' display too. Those options will then be effectively
53 * applied only if the view mode is configured to use them.
55 * hook_entity_view_display_alter() is invoked on each display, allowing 3rd
56 * party code to alter the display options held in the display before they are
57 * used to generate render arrays.
59 * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
60 * The entities being rendered. They should all be of the same entity type.
61 * @param string $view_mode
62 * The view mode being rendered.
64 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
65 * The display objects to use to render the entities, keyed by entity
68 * @see entity_get_display()
69 * @see hook_entity_view_display_alter()
71 public static function collectRenderDisplays($entities, $view_mode) {
72 if (empty($entities)) {
76 // Collect entity type and bundles.
77 $entity_type = current($entities)->getEntityTypeId();
79 foreach ($entities as $entity) {
80 $bundles[$entity->bundle()] = TRUE;
82 $bundles = array_keys($bundles);
84 // For each bundle, check the existence and status of:
85 // - the display for the view mode,
86 // - the 'default' display.
88 foreach ($bundles as $bundle) {
89 if ($view_mode != 'default') {
90 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
92 $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
94 $results = \Drupal::entityQuery('entity_view_display')
95 ->condition('id', NestedArray::mergeDeepArray($candidate_ids))
96 ->condition('status', TRUE)
99 // For each bundle, select the first valid candidate display, if any.
101 foreach ($bundles as $bundle) {
102 foreach ($candidate_ids[$bundle] as $candidate_id) {
103 if (isset($results[$candidate_id])) {
104 $load_ids[$bundle] = $candidate_id;
110 // Load the selected displays.
111 $storage = \Drupal::entityManager()->getStorage('entity_view_display');
112 $displays = $storage->loadMultiple($load_ids);
114 $displays_by_bundle = [];
115 foreach ($bundles as $bundle) {
116 // Use the selected display if any, or create a fresh runtime object.
117 if (isset($load_ids[$bundle])) {
118 $display = $displays[$load_ids[$bundle]];
121 $display = $storage->create([
122 'targetEntityType' => $entity_type,
124 'mode' => $view_mode,
129 // Let the display know which view mode was originally requested.
130 $display->originalMode = $view_mode;
132 // Let modules alter the display.
134 'entity_type' => $entity_type,
136 'view_mode' => $view_mode,
138 \Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
140 $displays_by_bundle[$bundle] = $display;
143 return $displays_by_bundle;
147 * Returns the display object used to render an entity.
149 * See the collectRenderDisplays() method for details.
151 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
152 * The entity being rendered.
153 * @param string $view_mode
156 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
157 * The display object that should be used to render the entity.
159 * @see \Drupal\Core\Entity\Entity\EntityViewDisplay::collectRenderDisplays()
161 public static function collectRenderDisplay(FieldableEntityInterface $entity, $view_mode) {
162 $displays = static::collectRenderDisplays([$entity], $view_mode);
163 return $displays[$entity->bundle()];
169 public function __construct(array $values, $entity_type) {
170 $this->pluginManager = \Drupal::service('plugin.manager.field.formatter');
172 parent::__construct($values, $entity_type);
178 public function postSave(EntityStorageInterface $storage, $update = TRUE) {
179 // Reset the render cache for the target entity type.
180 parent::postSave($storage, $update);
181 if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) {
182 \Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache();
189 public function getRenderer($field_name) {
190 if (isset($this->plugins[$field_name])) {
191 return $this->plugins[$field_name];
194 // Instantiate the formatter object from the stored display properties.
195 if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
196 $formatter = $this->pluginManager->getInstance([
197 'field_definition' => $definition,
198 'view_mode' => $this->originalMode,
199 // No need to prepare, defaults have been merged in setComponent().
201 'configuration' => $configuration
208 // Persist the formatter object.
209 $this->plugins[$field_name] = $formatter;
216 public function build(FieldableEntityInterface $entity) {
217 $build = $this->buildMultiple([$entity]);
224 public function buildMultiple(array $entities) {
226 foreach ($entities as $key => $entity) {
227 $build_list[$key] = [];
230 // Run field formatters.
231 foreach ($this->getComponents() as $name => $options) {
232 if ($formatter = $this->getRenderer($name)) {
233 // Group items across all entities and pass them to the formatter's
234 // prepareView() method.
236 foreach ($entities as $id => $entity) {
237 $items = $entity->get($name);
238 $items->filterEmptyItems();
239 $grouped_items[$id] = $items;
241 $formatter->prepareView($grouped_items);
243 // Then let the formatter build the output for each entity.
244 foreach ($entities as $id => $entity) {
245 $items = $grouped_items[$id];
246 /** @var \Drupal\Core\Access\AccessResultInterface $field_access */
247 $field_access = $items->access('view', NULL, TRUE);
248 // The language of the field values to display is already determined
249 // in the incoming $entity. The formatter should build its output of
250 // those values using:
251 // - the entity language if the entity is translatable,
252 // - the current "content language" otherwise.
253 if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
254 $view_langcode = $entity->language()->getId();
257 $view_langcode = NULL;
259 $build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $view_langcode) : [];
260 // Apply the field access cacheability metadata to the render array.
261 $this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);
266 foreach ($entities as $id => $entity) {
267 // Assign the configured weights.
268 foreach ($this->getComponents() as $name => $options) {
269 if (isset($build_list[$id][$name])) {
270 $build_list[$id][$name]['#weight'] = $options['weight'];
274 // Let other modules alter the renderable array.
277 'view_mode' => $this->originalMode,
280 \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$id], $context);
289 public function getPluginCollections() {
290 $configurations = [];
291 foreach ($this->getComponents() as $field_name => $configuration) {
292 if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
293 $configurations[$configuration['type']] = $configuration + [
294 'field_definition' => $field_definition,
295 'view_mode' => $this->originalMode,
301 'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)