PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

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

http://github.com/drupal/drupal
PHP | 307 lines | 140 code | 28 blank | 139 comment | 15 complexity | b5b653909a76c5bde45a8c388a1a3d3a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\views\Entity\Render;
  3. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  4. use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
  5. use Drupal\Core\Entity\Entity\EntityViewDisplay;
  6. use Drupal\Core\Entity\EntityRepositoryInterface;
  7. use Drupal\Core\Entity\EntityTypeInterface;
  8. use Drupal\Core\Entity\EntityTypeManagerInterface;
  9. use Drupal\Core\Language\LanguageManagerInterface;
  10. use Drupal\views\Plugin\views\field\EntityField;
  11. use Drupal\views\Plugin\views\query\QueryPluginBase;
  12. use Drupal\views\ResultRow;
  13. use Drupal\views\ViewExecutable;
  14. /**
  15. * Renders entity fields.
  16. *
  17. * This is used to build render arrays for all entity field values of a view
  18. * result set sharing the same relationship. An entity translation renderer is
  19. * used internally to handle entity language properly.
  20. */
  21. class EntityFieldRenderer extends RendererBase {
  22. use EntityTranslationRenderTrait;
  23. use DependencySerializationTrait;
  24. use DeprecatedServicePropertyTrait;
  25. /**
  26. * {@inheritdoc}
  27. */
  28. protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
  29. /**
  30. * The relationship being handled.
  31. *
  32. * @var string
  33. */
  34. protected $relationship;
  35. /**
  36. * The entity type manager.
  37. *
  38. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  39. */
  40. protected $entityTypeManager;
  41. /**
  42. * The entity repository service.
  43. *
  44. * @var \Drupal\Core\Entity\EntityRepositoryInterface
  45. */
  46. protected $entityRepository;
  47. /**
  48. * A list of indexes of rows whose fields have already been rendered.
  49. *
  50. * @var int[]
  51. */
  52. protected $processedRows = [];
  53. /**
  54. * Constructs an EntityFieldRenderer object.
  55. *
  56. * @param \Drupal\views\ViewExecutable $view
  57. * The view whose fields are being rendered.
  58. * @param string $relationship
  59. * The relationship to be handled.
  60. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  61. * The language manager.
  62. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  63. * The entity type.
  64. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  65. * The entity type manager.
  66. * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
  67. * The entity repository.
  68. */
  69. public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository = NULL) {
  70. parent::__construct($view, $language_manager, $entity_type);
  71. $this->relationship = $relationship;
  72. $this->entityTypeManager = $entity_type_manager;
  73. if (!$entity_repository) {
  74. @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);
  75. $entity_repository = \Drupal::service('entity.repository');
  76. }
  77. $this->entityRepository = $entity_repository;
  78. }
  79. /**
  80. * {@inheritdoc}
  81. */
  82. public function getCacheContexts() {
  83. return $this->getEntityTranslationRenderer()->getCacheContexts();
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getEntityTypeId() {
  89. return $this->entityType->id();
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. protected function getEntityManager() {
  95. // This relies on DeprecatedServicePropertyTrait to trigger a deprecation
  96. // message in case it is accessed.
  97. return $this->entityManager;
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. protected function getEntityTypeManager() {
  103. return $this->entityTypeManager;
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. protected function getEntityRepository() {
  109. return $this->entityRepository;
  110. }
  111. /**
  112. * {@inheritdoc}
  113. */
  114. protected function getLanguageManager() {
  115. return $this->languageManager;
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. protected function getView() {
  121. return $this->view;
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function query(QueryPluginBase $query, $relationship = NULL) {
  127. $this->getEntityTranslationRenderer()->query($query, $relationship);
  128. }
  129. /**
  130. * Renders entity field data.
  131. *
  132. * @param \Drupal\views\ResultRow $row
  133. * A single row of the query result.
  134. * @param \Drupal\views\Plugin\views\field\EntityField $field
  135. * (optional) A field to be rendered.
  136. *
  137. * @return array
  138. * A renderable array for the entity data contained in the result row.
  139. */
  140. public function render(ResultRow $row, EntityField $field = NULL) {
  141. // The method is called for each field in each result row. In order to
  142. // leverage multiple-entity building of formatter output, we build the
  143. // render arrays for all fields in all rows on the first call.
  144. if (!isset($this->build)) {
  145. $this->build = $this->buildFields($this->view->result);
  146. }
  147. if (isset($field)) {
  148. $field_id = $field->options['id'];
  149. // Pick the render array for the row / field we are being asked to render,
  150. // and remove it from $this->build to free memory as we progress.
  151. if (isset($this->build[$row->index][$field_id])) {
  152. $build = $this->build[$row->index][$field_id];
  153. unset($this->build[$row->index][$field_id]);
  154. }
  155. elseif (isset($this->build[$row->index])) {
  156. // In the uncommon case where a field gets rendered several times
  157. // (typically through direct Views API calls), the pre-computed render
  158. // array was removed by the unset() above. We have to manually rebuild
  159. // the render array for the row.
  160. $build = $this->buildFields([$row])[$row->index][$field_id];
  161. }
  162. else {
  163. // In case the relationship is optional, there might not be any fields
  164. // to render for this row.
  165. $build = [];
  166. }
  167. }
  168. else {
  169. // Same logic as above, in the case where we are being called for a whole
  170. // row.
  171. if (isset($this->build[$row->index])) {
  172. $build = $this->build[$row->index];
  173. unset($this->build[$row->index]);
  174. }
  175. else {
  176. $build = $this->buildFields([$row])[$row->index];
  177. }
  178. }
  179. return $build;
  180. }
  181. /**
  182. * Builds the render arrays for all fields of all result rows.
  183. *
  184. * The output is built using EntityViewDisplay objects to leverage
  185. * multiple-entity building and ensure a common code path with regular entity
  186. * view.
  187. * - Each relationship is handled by a separate EntityFieldRenderer instance,
  188. * since it operates on its own set of entities. This also ensures different
  189. * entity types are handled separately, as they imply different
  190. * relationships.
  191. * - Within each relationship, the fields to render are arranged in unique
  192. * sets containing each field at most once (an EntityViewDisplay can
  193. * only process a field once with given display options, but a View can
  194. * contain the same field several times with different display options).
  195. * - For each set of fields, entities are processed by bundle, so that
  196. * formatters can operate on the proper field definition for the bundle.
  197. *
  198. * @param \Drupal\views\ResultRow[] $values
  199. * An array of all ResultRow objects returned from the query.
  200. *
  201. * @return array
  202. * A renderable array for the fields handled by this renderer.
  203. *
  204. * @see \Drupal\Core\Entity\Entity\EntityViewDisplay
  205. */
  206. protected function buildFields(array $values) {
  207. $build = [];
  208. if ($values && ($field_ids = $this->getRenderableFieldIds())) {
  209. $entity_type_id = $this->getEntityTypeId();
  210. // Collect the entities for the relationship, fetch the right translation,
  211. // and group by bundle. For each result row, the corresponding entity can
  212. // be obtained from any of the fields handlers, so we arbitrarily use the
  213. // first one.
  214. $entities_by_bundles = [];
  215. $field = $this->view->field[current($field_ids)];
  216. foreach ($values as $result_row) {
  217. if ($entity = $field->getEntity($result_row)) {
  218. $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
  219. }
  220. }
  221. // Determine unique sets of fields that can be processed by the same
  222. // display. Fields that appear several times in the View open additional
  223. // "overflow" displays.
  224. $display_sets = [];
  225. foreach ($field_ids as $field_id) {
  226. $field = $this->view->field[$field_id];
  227. $field_name = $field->definition['field_name'];
  228. $index = 0;
  229. while (isset($display_sets[$index]['field_names'][$field_name])) {
  230. $index++;
  231. }
  232. $display_sets[$index]['field_names'][$field_name] = $field;
  233. $display_sets[$index]['field_ids'][$field_id] = $field;
  234. }
  235. // For each set of fields, build the output by bundle.
  236. foreach ($display_sets as $display_fields) {
  237. foreach ($entities_by_bundles as $bundle => $bundle_entities) {
  238. // Create the display, and configure the field display options.
  239. $display = EntityViewDisplay::create([
  240. 'targetEntityType' => $entity_type_id,
  241. 'bundle' => $bundle,
  242. 'status' => TRUE,
  243. ]);
  244. foreach ($display_fields['field_ids'] as $field) {
  245. $display->setComponent($field->definition['field_name'], [
  246. 'type' => $field->options['type'],
  247. 'settings' => $field->options['settings'],
  248. ]);
  249. }
  250. // Let the display build the render array for the entities.
  251. $display_build = $display->buildMultiple($bundle_entities);
  252. // Collect the field render arrays and index them using our internal
  253. // row indexes and field IDs.
  254. foreach ($display_build as $row_index => $entity_build) {
  255. foreach ($display_fields['field_ids'] as $field_id => $field) {
  256. $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : [];
  257. }
  258. }
  259. }
  260. }
  261. }
  262. return $build;
  263. }
  264. /**
  265. * Returns a list of names of entity fields to be rendered.
  266. *
  267. * @return string[]
  268. * An associative array of views fields.
  269. */
  270. protected function getRenderableFieldIds() {
  271. $field_ids = [];
  272. foreach ($this->view->field as $field_id => $field) {
  273. if ($field instanceof EntityField && $field->relationship == $this->relationship) {
  274. $field_ids[] = $field_id;
  275. }
  276. }
  277. return $field_ids;
  278. }
  279. }