From 9c88b1afcef87178dd27c802b986e59c3fb3a45c Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Mon, 10 Feb 2014 14:50:13 +0000
Subject: [PATCH] Issue #2090509 by yched, swentel, Wim Leers: Optimize
 entity_get_render_display() for the case of 'multiple entity view'.

---
 core/includes/entity.inc                      | 109 ---------------
 .../Core/Entity/EntityFormController.php      |   3 +-
 .../Drupal/Core/Entity/EntityViewBuilder.php  |   9 +-
 .../lib/Drupal/edit/Form/EditFieldForm.php    |   9 +-
 .../lib/Drupal/edit/MetadataGenerator.php     |   4 +-
 .../entity/Entity/EntityFormDisplay.php       |  77 +++++++++++
 .../entity/Entity/EntityViewDisplay.php       | 126 ++++++++++++++++++
 core/modules/field/field.module               |   3 +-
 8 files changed, 216 insertions(+), 124 deletions(-)

diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index eeb117b4f6d1..c2054c2d9790 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -569,62 +569,6 @@ function entity_get_display($entity_type, $bundle, $view_mode) {
   return $display;
 }
 
-/**
- * Returns the entity view display used to render an entity.
- *
- * Depending on the configuration of the view mode for the bundle, this can be
- * either the display object associated to the view mode, or the 'default'
- * display.
- *
- * This function should only be used internally when rendering an entity. When
- * assigning suggested display options for a component in a given view mode,
- * entity_get_display() should be used instead, in order to avoid inadvertently
- * modifying the output of other view modes that might happen to use the
- * 'default' display too. Those options will then be effectively applied only
- * if the view mode is configured to use them.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity being rendered.
- * @param string $view_mode
- *  The view mode being rendered.
- *
- * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
- *   The entity view display that should be used to render the entity.
- *
- * @see entity_get_display().
- */
-function entity_get_render_display(EntityInterface $entity, $view_mode) {
-  $entity_type = $entity->getEntityTypeId();
-  $bundle = $entity->bundle();
-  $render_view_mode = 'default';
-
-  // Fall back to the default display if the display for the view mode does
-  // not exist or is disabled.
-  if ($view_mode != 'default') {
-    $ids = array(
-      'default' => $entity_type . '.' . $bundle . '.default',
-      $view_mode => $entity_type . '.' . $bundle . '.' . $view_mode,
-    );
-    $entity_displays = entity_load_multiple('entity_view_display', $ids);
-    if (isset($entity_displays[$ids[$view_mode]]) && $entity_displays[$ids[$view_mode]]->status()) {
-      $render_view_mode = $view_mode;
-    }
-  }
-
-  $display = entity_get_display($entity_type, $bundle, $render_view_mode);
-  $display->originalMode = $view_mode;
-
-  // Let modules alter the display.
-  $display_context = array(
-    'entity_type' => $entity_type,
-    'bundle' => $bundle,
-    'view_mode' => $view_mode,
-  );
-  drupal_alter('entity_view_display', $display, $display_context);
-
-  return $display;
-}
-
 /**
  * Returns the entity form display associated to a bundle and form mode.
  *
@@ -681,59 +625,6 @@ function entity_get_form_display($entity_type, $bundle, $form_mode) {
   return $entity_form_display;
 }
 
-/**
- * Returns the entity form display used to render an entity form.
- *
- * This function should only be used internally when rendering an entity form.
- * When assigning suggested form display options for a component in a given form
- * mode, entity_get_form_display() should be used instead, in order to avoid
- * inadvertently modifying the output of other form modes that might happen to
- * use the 'default' form display too. Those options will then be effectively
- * applied only if the form mode is configured to use them.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity for which the form is being rendered.
- * @param string $form_mode
- *   The form mode being rendered.
- *
- * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
- *   The entity form display object that should be used to render the entity
- *   form.
- *
- * @see entity_get_form_display().
- */
-function entity_get_render_form_display(EntityInterface $entity, $form_mode) {
-  $entity_type = $entity->getEntityTypeId();
-  $bundle = $entity->bundle();
-  $render_form_mode = 'default';
-
-  // Look at the default form display and form display for the view mode, and
-  // fallback to the former if the latter does not exist is disabled.
-  if ($form_mode != 'default') {
-    $ids = array(
-      'default' => $entity_type . '.' . $bundle . '.default',
-      $form_mode => $entity_type . '.' . $bundle . '.' . $form_mode,
-    );
-    $entity_form_displays = entity_load_multiple('entity_form_display', $ids);
-    if (isset($entity_form_displays[$ids[$form_mode]]) && $entity_form_displays[$ids[$form_mode]]->status()) {
-      $render_form_mode = $form_mode;
-    }
-  }
-
-  $form_display = entity_get_form_display($entity_type, $bundle, $render_form_mode);
-  $form_display->originalMode = $form_mode;
-
-  // Let modules alter the form display.
-  $form_display_context = array(
-    'entity_type' => $entity_type,
-    'bundle' => $bundle,
-    'form_mode' => $form_mode,
-  );
-  drupal_alter('entity_form_display', $form_display, $form_display_context);
-
-  return $form_display;
-}
-
 /**
  * Generic access callback for entity pages.
  *
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 342d395949dc..440aa5091a59 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\entity\Entity\EntityFormDisplay;
 
 /**
  * Base class for entity form controllers.
@@ -121,7 +122,7 @@ protected function init(array &$form_state) {
     // Prepare the entity to be presented in the entity form.
     $this->prepareEntity();
 
-    $form_display = entity_get_render_form_display($this->entity, $this->getOperation());
+    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
     $this->setFormDisplay($form_display, $form_state);
 
     // Invoke the prepare form hooks.
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 05646ba73218..e67657c4cdcf 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -9,9 +9,9 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\entity\Entity\EntityViewDisplay;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -193,7 +193,6 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
 
     // Build the view modes and display objects.
     $view_modes = array();
-    $displays = array();
     $context = array('langcode' => $langcode);
     foreach ($entities as $key => $entity) {
       $bundle = $entity->bundle();
@@ -208,14 +207,10 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
       drupal_alter('entity_view_mode', $entity_view_mode, $entity, $context);
       // Store entities for rendering by view_mode.
       $view_modes[$entity_view_mode][$entity->id()] = $entity;
-
-      // Get the corresponding display settings.
-      if (!isset($displays[$entity_view_mode][$bundle])) {
-        $displays[$entity_view_mode][$bundle] = entity_get_render_display($entity, $entity_view_mode);
-      }
     }
 
     foreach ($view_modes as $mode => $view_mode_entities) {
+      $displays[$mode] = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $mode);
       $this->buildContent($view_mode_entities, $displays[$mode], $mode, $langcode);
     }
 
diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
index f6f5a9428723..e9302069d3f5 100644
--- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
+++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
@@ -7,13 +7,14 @@
 
 namespace Drupal\edit\Form;
 
-use Drupal\Core\Form\FormBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\entity\Entity\EntityFormDisplay;
 use Drupal\user\TempStoreFactory;
-use Drupal\Core\Entity\EntityChangedInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Builds and process a form for editing a single entity field.
@@ -128,7 +129,7 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name
 
     // @todo Allow the usage of different form modes by exposing a hook and the
     //   UI for them.
-    $form_state['form_display'] = entity_get_render_form_display($entity, 'default');
+    $form_state['form_display'] = EntityFormDisplay::collectRenderDisplay($entity, 'default');
   }
 
   /**
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
index e34e1dc03eae..2fd63ed0aefc 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
@@ -11,7 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\edit\Access\EditEntityFieldAccessCheckInterface;
-use Drupal\field\FieldInstanceInterface;
+use Drupal\entity\Entity\EntityViewDisplay;
 
 /**
  * Generates in-place editing metadata for an entity field.
@@ -78,7 +78,7 @@ public function generateFieldMetadata(FieldItemListInterface $items, $view_mode)
     }
 
     // Early-return if no editor is available.
-    $formatter_id = entity_get_render_display($entity, $view_mode)->getRenderer($field_name)->getPluginId();
+    $formatter_id = EntityViewDisplay::collectRenderDisplay($entity, $view_mode)->getRenderer($field_name)->getPluginId();
     $editor_id = $this->editorSelector->getEditor($formatter_id, $items);
     if (!isset($editor_id)) {
       return array('access' => FALSE);
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
index ec22794ba501..3222af79eb94 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
@@ -35,6 +35,83 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
    */
   protected $displayContext = 'form';
 
+  /**
+   * Returns the entity_form_display object used to build an entity form.
+   *
+   * Depending on the configuration of the form mode for the entity bundle, this
+   * can be either the display object associated to the form mode, or the
+   * 'default' display.
+   *
+   * This method should only be used internally when rendering an entity form.
+   * When assigning suggested display options for a component in a given form
+   * mode, entity_get_form_display() should be used instead, in order to avoid
+   * inadvertently modifying the output of other form modes that might happen to
+   * use the 'default' display too. Those options will then be effectively
+   * applied only if the form mode is configured to use them.
+   *
+   * hook_entity_form_display_alter() is invoked on each display, allowing 3rd
+   * party code to alter the display options held in the display before they are
+   * used to generate render arrays.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the form is being built.
+   * @param string $form_mode
+   *   The form mode.
+   *
+   * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
+   *   The display object that should be used to build the entity form.
+   *
+   * @see entity_get_form_display()
+   * @see hook_entity_form_display_alter()
+   */
+  public static function collectRenderDisplay($entity, $form_mode) {
+    $entity_type = $entity->getEntityTypeId();
+    $bundle = $entity->bundle();
+
+    // Check the existence and status of:
+    // - the display for the form mode,
+    // - the 'default' display.
+    if ($form_mode != 'default') {
+      $candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode;
+    }
+    $candidate_ids[] = $entity_type . '.' . $bundle . '.default';
+    $results = \Drupal::entityQuery('entity_form_display')
+      ->condition('id', $candidate_ids)
+      ->condition('status', TRUE)
+      ->execute();
+
+    // Load the first valid candidate display, if any.
+    $storage = \Drupal::entityManager()->getStorageController('entity_form_display');
+    foreach ($candidate_ids as $candidate_id) {
+      if (isset($results[$candidate_id])) {
+        $display = $storage->load($candidate_id);
+        break;
+      }
+    }
+    // Else create a fresh runtime object.
+    if (empty($display)) {
+      $display = $storage->create(array(
+        'targetEntityType' => $entity_type,
+        'bundle' => $bundle,
+        'mode' => $form_mode,
+        'status' => TRUE,
+      ));
+    }
+
+    // Let the display know which form mode was originally requested.
+    $display->originalMode = $form_mode;
+
+    // Let modules alter the display.
+    $display_context = array(
+      'entity_type' => $entity_type,
+      'bundle' => $bundle,
+      'form_mode' => $form_mode,
+    );
+    \Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context);
+
+    return $display;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
index 9e6642fc599c..b34259b243e5 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\entity\Entity;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\entity\EntityDisplayBase;
 
@@ -35,6 +36,131 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
    */
   protected $displayContext = 'view';
 
+  /**
+   * Returns the display objects used to render a set of entities.
+   *
+   * Depending on the configuration of the view mode for each bundle, this can
+   * be either the display object associated to the view mode, or the 'default'
+   * display.
+   *
+   * This method should only be used internally when rendering an entity. When
+   * assigning suggested display options for a component in a given view mode,
+   * entity_get_display() should be used instead, in order to avoid
+   * inadvertently modifying the output of other view modes that might happen to
+   * use the 'default' display too. Those options will then be effectively
+   * applied only if the view mode is configured to use them.
+   *
+   * hook_entity_view_display_alter() is invoked on each display, allowing 3rd
+   * party code to alter the display options held in the display before they are
+   * used to generate render arrays.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   The entities being rendered. They should all be of the same entity type.
+   * @param string $view_mode
+   *   The view mode being rendered.
+   *
+   * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[]
+   *   The display objects to use to render the entities, keyed by entity
+   *   bundle.
+   *
+   * @see entity_get_display()
+   * @see hook_entity_view_display_alter()
+   */
+  public static function collectRenderDisplays($entities, $view_mode) {
+    if (empty($entities)) {
+      return array();
+    }
+
+    // Collect entity type and bundles.
+    $entity_type = current($entities)->getEntityTypeId();
+    $bundles = array();
+    foreach ($entities as $entity) {
+      $bundles[$entity->bundle()] = TRUE;
+    }
+    $bundles = array_keys($bundles);
+
+    // For each bundle, check the existence and status of:
+    // - the display for the view mode,
+    // - the 'default' display.
+    $candidate_ids = array();
+    foreach ($bundles as $bundle) {
+      if ($view_mode != 'default') {
+        $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.' . $view_mode;
+      }
+      $candidate_ids[$bundle][] = $entity_type . '.' . $bundle . '.default';
+    }
+    $results = \Drupal::entityQuery('entity_view_display')
+      ->condition('id', NestedArray::mergeDeepArray($candidate_ids))
+      ->condition('status', TRUE)
+      ->execute();
+
+    // For each bundle, select the first valid candidate display, if any.
+    $load_ids = array();
+    foreach ($bundles as $bundle) {
+      foreach ($candidate_ids[$bundle] as $candidate_id) {
+        if (isset($results[$candidate_id])) {
+          $load_ids[$bundle] = $candidate_id;
+          break;
+        }
+      }
+    }
+
+    // Load the selected displays.
+    $storage = \Drupal::entityManager()->getStorageController('entity_view_display');
+    $displays = $storage->loadMultiple($load_ids);
+
+    $displays_by_bundle = array();
+    foreach ($bundles as $bundle) {
+      // Use the selected display if any, or create a fresh runtime object.
+      if (isset($load_ids[$bundle])) {
+        $display = $displays[$load_ids[$bundle]];
+      }
+      else {
+        $display = $storage->create(array(
+          'targetEntityType' => $entity_type,
+          'bundle' => $bundle,
+          'mode' => $view_mode,
+          'status' => TRUE,
+        ));
+      }
+
+      // Let the display know which view mode was originally requested.
+      $display->originalMode = $view_mode;
+
+      // Let modules alter the display.
+      $display_context = array(
+        'entity_type' => $entity_type,
+        'bundle' => $bundle,
+        'view_mode' => $view_mode,
+      );
+      \Drupal::moduleHandler()->alter('entity_view_display', $display, $display_context);
+
+      $displays_by_bundle[$bundle] = $display;
+    }
+
+    return $displays_by_bundle;
+  }
+
+  /**
+   * Returns the display object used to render an entity.
+   *
+   * See the collectRenderDisplays() method for details.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being rendered.
+   * @param string $view_mode
+   *   The view mode.
+   *
+   * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
+   *   The display object that should be used to render the entity.
+   *
+   * @see \Drupal\entity\Entity\EntityDisplay::collectRenderDisplays()
+   */
+  public static function collectRenderDisplay($entity, $view_mode) {
+    $displays = static::collectRenderDisplays(array($entity), $view_mode);
+    return $displays[$entity->bundle()];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 62ca21fc288a..883814d2a0c2 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -7,6 +7,7 @@
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Template\Attribute;
+use Drupal\entity\Entity\EntityViewDisplay;
 
 /*
  * Load all public Field API functions. Drupal currently has no
@@ -428,7 +429,7 @@ function field_view_field(ContentEntityInterface $entity, $field_name, $display_
   // Get the formatter object.
   if (is_string($display_options)) {
     $view_mode = $display_options;
-    $formatter = entity_get_render_display($entity, $view_mode)->getRenderer($field_name);
+    $formatter = EntityViewDisplay::collectRenderDisplay($entity, $view_mode)->getRenderer($field_name);
   }
   else {
     $view_mode = '_custom';
-- 
GitLab