EntityFieldRenderer.php

Same filename in other branches
  1. 9 core/modules/views/src/Entity/Render/EntityFieldRenderer.php
  2. 10 core/modules/views/src/Entity/Render/EntityFieldRenderer.php
  3. 11.x core/modules/views/src/Entity/Render/EntityFieldRenderer.php

Namespace

Drupal\views\Entity\Render

File

core/modules/views/src/Entity/Render/EntityFieldRenderer.php

View source
<?php

namespace Drupal\views\Entity\Render;

use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\views\field\EntityField;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;

/**
 * Renders entity fields.
 *
 * This is used to build render arrays for all entity field values of a view
 * result set sharing the same relationship. An entity translation renderer is
 * used internally to handle entity language properly.
 */
class EntityFieldRenderer extends RendererBase {
    use EntityTranslationRenderTrait;
    use DependencySerializationTrait;
    use DeprecatedServicePropertyTrait;
    
    /**
     * {@inheritdoc}
     */
    protected $deprecatedProperties = [
        'entityManager' => 'entity.manager',
    ];
    
    /**
     * The relationship being handled.
     *
     * @var string
     */
    protected $relationship;
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * The entity repository service.
     *
     * @var \Drupal\Core\Entity\EntityRepositoryInterface
     */
    protected $entityRepository;
    
    /**
     * A list of indexes of rows whose fields have already been rendered.
     *
     * @var int[]
     */
    protected $processedRows = [];
    
    /**
     * Constructs an EntityFieldRenderer object.
     *
     * @param \Drupal\views\ViewExecutable $view
     *   The view whose fields are being rendered.
     * @param string $relationship
     *   The relationship to be handled.
     * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
     *   The language manager.
     * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
     *   The entity type.
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager.
     * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
     *   The entity repository.
     */
    public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository = NULL) {
        parent::__construct($view, $language_manager, $entity_type);
        $this->relationship = $relationship;
        $this->entityTypeManager = $entity_type_manager;
        if (!$entity_repository) {
            @trigger_error('Calling EntityFieldRenderer::__construct() with the $entity_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
            $entity_repository = \Drupal::service('entity.repository');
        }
        $this->entityRepository = $entity_repository;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getCacheContexts() {
        return $this->getEntityTranslationRenderer()
            ->getCacheContexts();
    }
    
    /**
     * {@inheritdoc}
     */
    public function getEntityTypeId() {
        return $this->entityType
            ->id();
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getEntityManager() {
        // This relies on DeprecatedServicePropertyTrait to trigger a deprecation
        // message in case it is accessed.
        return $this->entityManager;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getEntityTypeManager() {
        return $this->entityTypeManager;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getEntityRepository() {
        return $this->entityRepository;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getLanguageManager() {
        return $this->languageManager;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getView() {
        return $this->view;
    }
    
    /**
     * {@inheritdoc}
     */
    public function query(QueryPluginBase $query, $relationship = NULL) {
        $this->getEntityTranslationRenderer()
            ->query($query, $relationship);
    }
    
    /**
     * Renders entity field data.
     *
     * @param \Drupal\views\ResultRow $row
     *   A single row of the query result.
     * @param \Drupal\views\Plugin\views\field\EntityField $field
     *   (optional) A field to be rendered.
     *
     * @return array
     *   A renderable array for the entity data contained in the result row.
     */
    public function render(ResultRow $row, EntityField $field = NULL) {
        // The method is called for each field in each result row. In order to
        // leverage multiple-entity building of formatter output, we build the
        // render arrays for all fields in all rows on the first call.
        if (!isset($this->build)) {
            $this->build = $this->buildFields($this->view->result);
        }
        if (isset($field)) {
            $field_id = $field->options['id'];
            // Pick the render array for the row / field we are being asked to render,
            // and remove it from $this->build to free memory as we progress.
            if (isset($this->build[$row->index][$field_id])) {
                $build = $this->build[$row->index][$field_id];
                unset($this->build[$row->index][$field_id]);
            }
            elseif (isset($this->build[$row->index])) {
                // In the uncommon case where a field gets rendered several times
                // (typically through direct Views API calls), the pre-computed render
                // array was removed by the unset() above. We have to manually rebuild
                // the render array for the row.
                $build = $this->buildFields([
                    $row,
                ])[$row->index][$field_id];
            }
            else {
                // In case the relationship is optional, there might not be any fields
                // to render for this row.
                $build = [];
            }
        }
        else {
            // Same logic as above, in the case where we are being called for a whole
            // row.
            if (isset($this->build[$row->index])) {
                $build = $this->build[$row->index];
                unset($this->build[$row->index]);
            }
            else {
                $build = $this->buildFields([
                    $row,
                ])[$row->index];
            }
        }
        return $build;
    }
    
    /**
     * Builds the render arrays for all fields of all result rows.
     *
     * The output is built using EntityViewDisplay objects to leverage
     * multiple-entity building and ensure a common code path with regular entity
     * view.
     * - Each relationship is handled by a separate EntityFieldRenderer instance,
     *   since it operates on its own set of entities. This also ensures different
     *   entity types are handled separately, as they imply different
     *   relationships.
     * - Within each relationship, the fields to render are arranged in unique
     *   sets containing each field at most once (an EntityViewDisplay can
     *   only process a field once with given display options, but a View can
     *   contain the same field several times with different display options).
     * - For each set of fields, entities are processed by bundle, so that
     *   formatters can operate on the proper field definition for the bundle.
     *
     * @param \Drupal\views\ResultRow[] $values
     *   An array of all ResultRow objects returned from the query.
     *
     * @return array
     *   A renderable array for the fields handled by this renderer.
     *
     * @see \Drupal\Core\Entity\Entity\EntityViewDisplay
     */
    protected function buildFields(array $values) {
        $build = [];
        if ($values && ($field_ids = $this->getRenderableFieldIds())) {
            $entity_type_id = $this->getEntityTypeId();
            // Collect the entities for the relationship, fetch the right translation,
            // and group by bundle. For each result row, the corresponding entity can
            // be obtained from any of the fields handlers, so we arbitrarily use the
            // first one.
            $entities_by_bundles = [];
            $field = $this->view->field[current($field_ids)];
            foreach ($values as $result_row) {
                if ($entity = $field->getEntity($result_row)) {
                    $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
                }
            }
            // Determine unique sets of fields that can be processed by the same
            // display. Fields that appear several times in the View open additional
            // "overflow" displays.
            $display_sets = [];
            foreach ($field_ids as $field_id) {
                $field = $this->view->field[$field_id];
                $field_name = $field->definition['field_name'];
                $index = 0;
                while (isset($display_sets[$index]['field_names'][$field_name])) {
                    $index++;
                }
                $display_sets[$index]['field_names'][$field_name] = $field;
                $display_sets[$index]['field_ids'][$field_id] = $field;
            }
            // For each set of fields, build the output by bundle.
            foreach ($display_sets as $display_fields) {
                foreach ($entities_by_bundles as $bundle => $bundle_entities) {
                    // Create the display, and configure the field display options.
                    $display = EntityViewDisplay::create([
                        'targetEntityType' => $entity_type_id,
                        'bundle' => $bundle,
                        'status' => TRUE,
                    ]);
                    foreach ($display_fields['field_ids'] as $field) {
                        $display->setComponent($field->definition['field_name'], [
                            'type' => $field->options['type'],
                            'settings' => $field->options['settings'],
                        ]);
                    }
                    // Let the display build the render array for the entities.
                    $display_build = $display->buildMultiple($bundle_entities);
                    // Collect the field render arrays and index them using our internal
                    // row indexes and field IDs.
                    foreach ($display_build as $row_index => $entity_build) {
                        foreach ($display_fields['field_ids'] as $field_id => $field) {
                            $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : [];
                        }
                    }
                }
            }
        }
        return $build;
    }
    
    /**
     * Returns a list of names of entity fields to be rendered.
     *
     * @return string[]
     *   An associative array of views fields.
     */
    protected function getRenderableFieldIds() {
        $field_ids = [];
        foreach ($this->view->field as $field_id => $field) {
            if ($field instanceof EntityField && $field->relationship == $this->relationship) {
                $field_ids[] = $field_id;
            }
        }
        return $field_ids;
    }

}

Classes

Title Deprecated Summary
EntityFieldRenderer Renders entity fields.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.