_referringItem. This is useful for field types * that store additional values next to the reference itself. * * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items * The item list. * @param string $langcode * The language code of the referenced entities to display. * * @return \Drupal\Core\Entity\EntityInterface[] * The array of referenced entities to display, keyed by delta. * * @see ::prepareView() */ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) { $entities = []; foreach ($items as $delta => $item) { // Ignore items where no entity could be loaded in prepareView(). if (!empty($item->_loaded)) { $entity = $item->entity; // Set the entity in the correct language for display. if ($entity instanceof TranslatableInterface) { $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode); } $access = $this->checkAccess($entity); // Add the access result's cacheability, ::view() needs it. $item->_accessCacheability = CacheableMetadata::createFromObject($access); if ($access->isAllowed()) { // Add the referring item, in case the formatter needs it. $entity->_referringItem = $items[$delta]; $entities[$delta] = $entity; } } } return $entities; } /** * {@inheritdoc} * * @see ::prepareView() * @see ::getEntitiestoView() */ public function view(FieldItemListInterface $items, $langcode = NULL) { $elements = parent::view($items, $langcode); $field_level_access_cacheability = new CacheableMetadata(); // Try to map the cacheability of the access result that was set at // _accessCacheability in getEntitiesToView() to the corresponding render // subtree. If no such subtree is found, then merge it with the field-level // access cacheability. foreach ($items as $delta => $item) { // Ignore items for which access cacheability could not be determined in // prepareView(). if (!empty($item->_accessCacheability)) { if (isset($elements[$delta])) { CacheableMetadata::createFromRenderArray($elements[$delta]) ->merge($item->_accessCacheability) ->applyTo($elements[$delta]); } else { $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability); } } } // Apply the cacheability metadata for the inaccessible entities and the // entities for which the corresponding render subtree could not be found. // This causes the field to be rendered (and cached) according to the cache // contexts by which the access results vary, to ensure only users with // access to this field can view it. It also tags this field with the cache // tags on which the access results depend, to ensure users that cannot view // this field at the moment will gain access once any of those cache tags // are invalidated. $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements)) ->applyTo($elements); return $elements; } /** * {@inheritdoc} * * Loads the entities referenced in that field across all the entities being * viewed. */ public function prepareView(array $entities_items) { // Collect entity IDs to load. For performance, we want to use a single // "multiple entity load" to load all the entities for the multiple // "entity reference item lists" being displayed. We thus cannot use // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities(). $ids = []; foreach ($entities_items as $items) { foreach ($items as $item) { // To avoid trying to reload non-existent entities in // getEntitiesToView(), explicitly mark the items where $item->entity // contains a valid entity ready for display. All items are initialized // at FALSE. $item->_loaded = FALSE; if ($this->needsEntityLoad($item)) { $ids[] = $item->target_id; } } } if ($ids) { $target_type = $this->getFieldSetting('target_type'); $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids); } // For each item, pre-populate the loaded entity in $item->entity, and set // the 'loaded' flag. foreach ($entities_items as $items) { foreach ($items as $item) { if (isset($target_entities[$item->target_id])) { $item->entity = $target_entities[$item->target_id]; $item->_loaded = TRUE; } elseif ($item->hasNewEntity()) { $item->_loaded = TRUE; } } } } /** * Returns whether the entity referenced by an item needs to be loaded. * * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item * The item to check. * * @return bool * TRUE if the entity needs to be loaded. */ protected function needsEntityLoad(EntityReferenceItem $item) { return !$item->hasNewEntity(); } /** * Checks access to the given entity. * * By default, entity 'view' access is checked. However, a subclass can choose * to exclude certain items from entity access checking by immediately * granting access. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity to check. * * @return \Drupal\Core\Access\AccessResult * A cacheable access result. */ protected function checkAccess(EntityInterface $entity) { return $entity->access('view', NULL, TRUE); } }