3 namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
8 use Drupal\Core\Field\FieldItemListInterface;
9 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
10 use Drupal\Core\Field\FormatterBase;
11 use Drupal\Core\TypedData\TranslatableInterface;
14 * Parent plugin for entity reference formatters.
16 abstract class EntityReferenceFormatterBase extends FormatterBase {
19 * Returns the referenced entities for display.
21 * The method takes care of:
22 * - checking entity access,
23 * - placing the entities in the language expected for display.
24 * It is thus strongly recommended that formatters use it in their
25 * implementation of viewElements($items) rather than dealing with $items
28 * For each entity, the EntityReferenceItem by which the entity is referenced
29 * is available in $entity->_referringItem. This is useful for field types
30 * that store additional values next to the reference itself.
32 * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
34 * @param string $langcode
35 * The language code of the referenced entities to display.
37 * @return \Drupal\Core\Entity\EntityInterface[]
38 * The array of referenced entities to display, keyed by delta.
40 * @see ::prepareView()
42 protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
45 foreach ($items as $delta => $item) {
46 // Ignore items where no entity could be loaded in prepareView().
47 if (!empty($item->_loaded)) {
48 $entity = $item->entity;
50 // Set the entity in the correct language for display.
51 if ($entity instanceof TranslatableInterface) {
52 $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
55 $access = $this->checkAccess($entity);
56 // Add the access result's cacheability, ::view() needs it.
57 $item->_accessCacheability = CacheableMetadata::createFromObject($access);
58 if ($access->isAllowed()) {
59 // Add the referring item, in case the formatter needs it.
60 $entity->_referringItem = $items[$delta];
61 $entities[$delta] = $entity;
72 * @see ::prepareView()
73 * @see ::getEntitiestoView()
75 public function view(FieldItemListInterface $items, $langcode = NULL) {
76 $elements = parent::view($items, $langcode);
78 $field_level_access_cacheability = new CacheableMetadata();
80 // Try to map the cacheability of the access result that was set at
81 // _accessCacheability in getEntitiesToView() to the corresponding render
82 // subtree. If no such subtree is found, then merge it with the field-level
83 // access cacheability.
84 foreach ($items as $delta => $item) {
85 // Ignore items for which access cacheability could not be determined in
87 if (!empty($item->_accessCacheability)) {
88 if (isset($elements[$delta])) {
89 CacheableMetadata::createFromRenderArray($elements[$delta])
90 ->merge($item->_accessCacheability)
91 ->applyTo($elements[$delta]);
94 $field_level_access_cacheability = $field_level_access_cacheability->merge($item->_accessCacheability);
99 // Apply the cacheability metadata for the inaccessible entities and the
100 // entities for which the corresponding render subtree could not be found.
101 // This causes the field to be rendered (and cached) according to the cache
102 // contexts by which the access results vary, to ensure only users with
103 // access to this field can view it. It also tags this field with the cache
104 // tags on which the access results depend, to ensure users that cannot view
105 // this field at the moment will gain access once any of those cache tags
107 $field_level_access_cacheability->merge(CacheableMetadata::createFromRenderArray($elements))
108 ->applyTo($elements);
116 * Loads the entities referenced in that field across all the entities being
119 public function prepareView(array $entities_items) {
120 // Collect entity IDs to load. For performance, we want to use a single
121 // "multiple entity load" to load all the entities for the multiple
122 // "entity reference item lists" being displayed. We thus cannot use
123 // \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
125 foreach ($entities_items as $items) {
126 foreach ($items as $item) {
127 // To avoid trying to reload non-existent entities in
128 // getEntitiesToView(), explicitly mark the items where $item->entity
129 // contains a valid entity ready for display. All items are initialized
131 $item->_loaded = FALSE;
132 if ($this->needsEntityLoad($item)) {
133 $ids[] = $item->target_id;
138 $target_type = $this->getFieldSetting('target_type');
139 $target_entities = \Drupal::entityManager()->getStorage($target_type)->loadMultiple($ids);
142 // For each item, pre-populate the loaded entity in $item->entity, and set
143 // the 'loaded' flag.
144 foreach ($entities_items as $items) {
145 foreach ($items as $item) {
146 if (isset($target_entities[$item->target_id])) {
147 $item->entity = $target_entities[$item->target_id];
148 $item->_loaded = TRUE;
150 elseif ($item->hasNewEntity()) {
151 $item->_loaded = TRUE;
158 * Returns whether the entity referenced by an item needs to be loaded.
160 * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
164 * TRUE if the entity needs to be loaded.
166 protected function needsEntityLoad(EntityReferenceItem $item) {
167 return !$item->hasNewEntity();
171 * Checks access to the given entity.
173 * By default, entity 'view' access is checked. However, a subclass can choose
174 * to exclude certain items from entity access checking by immediately
177 * @param \Drupal\Core\Entity\EntityInterface $entity
178 * The entity to check.
180 * @return \Drupal\Core\Access\AccessResult
181 * A cacheable access result.
183 protected function checkAccess(EntityInterface $entity) {
184 return $entity->access('view', NULL, TRUE);