diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 4a207a6..f4433c0 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -149,11 +149,28 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco '#langcode' => $langcode, ); + // Collect cache defaults for this entity. $build = $this->getBuildCacheDefaults($build, $entity, $view_mode, $langcode); return $build; } + /** + * Provides entity-specific cache defaults to the build process. + * + * @param array $build + * A renderable array containing build information and context for an + * entity view. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity for which the defaults should be provided. + * @param string $view_mode + * The view mode that should be used. + * @param string $langcode + * (optional) For which language the entity should be prepared, defaults to + * the current content language. + * + * @return array + */ protected function getBuildCacheDefaults(array $build, EntityInterface $entity, $view_mode, $langcode) { $build += array( '#cache' => array( @@ -183,11 +200,15 @@ protected function getBuildCacheDefaults(array $build, EntityInterface $entity, } /** - * Builds an entity's view on an entity view; augments entity defaults. + * Builds an entity's view; augments entity defaults. * * This function is assigned as a #pre_render callback in * \Drupal\Core\Entity\EntityViewBuilder::getBuildDefaults(). * + * By delaying the building of an entity until the #pre_render processing in + * drupal_render(), the processing cost of assembling an entity's renderable + * array is saved on cache-hit requests. + * * @param array $build * A renderable array containing build information and context for an * entity view. @@ -215,28 +236,32 @@ public function entityViewBuilderBuildSingle(array $build) { $build = $build_per_entity[$id]; // Allow for alterations while building, before rendering. - $this->moduleHandler()->invokeAll($view_hook, array(&$build, $entity, $display, $view_mode, $langcode)); - $this->moduleHandler()->invokeAll('entity_view', array(&$build, $entity, $display, $view_mode, $langcode)); - $this->alterBuild($build, $entity, $display, $view_mode, $langcode); + $this->callViewHooks($build, $entity, $display, $view_mode, $langcode); - // Assign the weights configured in the display. - // @todo: Once https://drupal.org/node/1875974 provides the missing API, - // only do it for 'extra fields', since other components have been taken - // care of in EntityViewDisplay::buildMultiple(). - foreach ($display->getComponents() as $name => $options) { - if (isset($build[$name])) { - $build[$name]['#weight'] = $options['weight']; - } - } - - // Allow modules to modify the render array. - \Drupal::moduleHandler()->alter(array($view_hook, 'entity_view'), $build, $entity, $display); return $build; } /** + * Builds multiple entities' views; augments entity defaults. + * + * This function is assigned as a #pre_render callback in + * \Drupal\Core\Entity\EntityViewBuilder::viewMultiple(). Each entity will + * also have a #pre_render callback in its build array for single building. + * That callback in removed for entities built in this multiple handling + * process so that any entity is only built once. + * + * By delaying the building of an entity until the #pre_render processing in + * drupal_render(), the processing cost of assembling an entity's renderable + * array is saved on cache-hit requests. + * * @param array $build + * A renderable array containing build information and context for an + * entity view. + * * @return array + * The updated renderable array. + * + * @see drupal_render() */ public function entityViewBuilderBuildMultiple(array $build) { // Build the view modes and display objects. @@ -244,67 +269,79 @@ public function entityViewBuilderBuildMultiple(array $build) { $langcode = $build['#langcode']; $entity_type_key = "#{$this->entityTypeId}"; - // Find the keys for the ContentEntities in the build. + // Find the keys for the ContentEntities in the build; Store entities for + // rendering by view_mode. $children = Element::children($build); - $entity_keys = array(); - foreach($children as $key) { if (isset($build[$key][$entity_type_key])) { $entity = $build[$key][$entity_type_key]; if ($entity instanceof ContentEntityInterface) { - $entity_keys[] = $key; + $view_modes[$build[$key]['#view_mode']][$key] = $entity; } } } - // Store entities for rendering by view_mode. - foreach($entity_keys as $key) { - $entity = $build[$key][$entity_type_key]; - $view_modes[$build[$key]['#view_mode']][$key] = $entity; - } - // Build content for the displays represented by the entities. - foreach ($view_modes as $mode => $view_mode_entities) { - $displays[$mode] = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $mode); - $this->buildContent($build, $view_mode_entities, $displays[$mode], $mode, $langcode); - // Remove the single pre_render function from the multiple build items. + foreach ($view_modes as $view_mode => $view_mode_entities) { + $displays = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $view_mode); + $this->buildContent($build, $view_mode_entities, $displays, $view_mode, $langcode); foreach (array_keys($view_mode_entities) as $key) { - $pre_render = $build[$key]['#pre_render']; + // Remove the single item pre_render function from the multiple build items. + $pre_render = $build[$key]['#pre_render'] ?: array(); foreach ($pre_render as $index => $callable) { if (is_array($callable) && $callable[1] === 'entityViewBuilderBuildSingle') { unset($build[$key]['#pre_render'][$index]); } } + // Allow for alterations while building, before rendering. + $entity = $build[$key][$entity_type_key]; + $display = $displays[$entity->bundle()]; + $this->callViewHooks($build[$key], $entity, $display, $view_mode, $langcode); } } + return $build; + } + + /** + * Wraps calls to the various hook invocations for entity view building. + * + * The hook invocations are shared between entityViewBuilderBuildSingle() and + * entityViewBuilderBuildMultiple(). + * + * @param array &$build + * The render array that is being created. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be prepared. + * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display + * The entity view display holding the display options configured for the + * entity components. + * @param string $view_mode + * The view mode that should be used to prepare the entity. + * @param string $langcode + * For which language the entity should be prepared, defaults to + * the current content language. + */ + private function callViewHooks(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) { $view_hook = "{$this->entityTypeId}_view"; - foreach ($entity_keys as $key) { - $entity = $build[$key][$entity_type_key]; - $view_mode = $build[$key]['#view_mode']; - $display = $displays[$view_mode][$entity->bundle()]; - // Allow for alterations while building, before rendering. - $this->moduleHandler()->invokeAll($view_hook, array(&$build[$key], $entity, $display, $view_mode, $langcode)); - $this->moduleHandler()->invokeAll('entity_view', array(&$build[$key], $entity, $display, $view_mode, $langcode)); - - $this->alterBuild($build[$key], $entity, $display, $view_mode, $langcode); - - // Assign the weights configured in the display. - // @todo: Once https://drupal.org/node/1875974 provides the missing API, - // only do it for 'extra fields', since other components have been taken - // care of in EntityViewDisplay::buildMultiple(). - foreach ($display->getComponents() as $name => $options) { - if (isset($build[$key][$name])) { - $build[$key]['#weight'] = $options['weight']; - } - } - // Allow modules to modify the render array. - \Drupal::moduleHandler()->alter(array($view_hook, 'entity_view'), $build[$key], $entity, $display); + $this->moduleHandler()->invokeAll($view_hook, array(&$build, $entity, $display, $view_mode, $langcode)); + $this->moduleHandler()->invokeAll('entity_view', array(&$build, $entity, $display, $view_mode, $langcode)); + $this->alterBuild($build, $entity, $display, $view_mode, $langcode); + + // Assign the weights configured in the display. + // @todo: Once https://drupal.org/node/1875974 provides the missing API, + // only do it for 'extra fields', since other components have been taken + // care of in EntityViewDisplay::buildMultiple(). + foreach ($display->getComponents() as $name => $options) { + if (isset($build[$name])) { + $build['#weight'] = $options['weight']; + } } - return $build; + // Allow modules to modify the render array. + \Drupal::moduleHandler()->alter(array($view_hook, 'entity_view'), $build, $entity, $display); } /**