Problem/Motivation

EntityReferenceFieldItemList::referencedEntities in its current implementation will collect all the target ids from the field items and then collect the referenced entity objects by calling loadMultiple, but this is faulty in the following use cases:

  1. An entity cloned object has been set on some of the field items -> load will not return this one but a different one.
  2. resetCache is called after the entity objects have been initialized on some of the field items.

Proposed resolution

EntityReferenceFieldItemList::referencedEntities should first collect the entities which are already set on the field items and load the remaining ones through loadMultiple.

Remaining tasks

  • Add test coverage
  • Review

User interface changes

None.

API changes

None.

Data model changes

None.

Issue fork drupal-2826717

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

hchonov created an issue. See original summary.

hchonov’s picture

The same actually applies for the entity_reference_revisions module where the function is 1:1 copy of its parent just there loadRevision is being called instead of loadMultiple.

jibran’s picture

Issue tags: +DER issue

This applies to DER as well.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Issue tags: +Bug Smash Initiative

This issue was the random bugsmash bug to triage a few days ago, adding tag.

It looks like this is waiting on a decision. I'll see if I can ping @hchonov in Slack.

What can be done to make a decision here?

jibran’s picture

@hchonov is the one who purposed the solutions so we need @berdir, @amateescu, or @catch's opinion on the issue.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dieterholvoet’s picture

EntityReferenceFieldItemList::referencedEntities should just call $this->getValue($include_computed=TRUE) and then from the result leave only the entity objects.

Drupal\Core\TypedData\Plugin\DataType\ItemList::getValue() doesn't have an $include_computed parameter, so that option is off the table.

I'll create a MR implementing the first option.

dieterholvoet’s picture

Title: EntityReferenceFieldItemList::referencedEntities might not return the entity objects from the list after resetCache call » EntityReferenceFieldItemList::referencedEntities might not return up-to-date entity objects

dieterholvoet’s picture

Status: Active » Needs review
smustgrave’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative, +Needs tests

There are 2 options it appears in the issue summary proposed solution. Can it be highlighted which was chosen?

Also could use a test case showing the issue.

dieterholvoet’s picture

Issue summary: View changes

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dieterholvoet’s picture

It seems like the current implementation of the MR causes a performance problem. $item->entity !== NULL triggers DataReferenceBase::getValue(), which triggers EntityReference::getTarget(), which triggers EntityStorageInterface::load(). This means that every entity is now loaded separately. I'll look for a different solution.

dieterholvoet’s picture

I found a fix. In a custom API endpoint with a bunch of referencedEntities() calls this makes a huge performance difference: -5.62 s (-75%) and -2 219 database queries. Hopefully anyone using a patch from this issue sees this.

berdir’s picture

Yes, that's the problem with accessing the property, it auto-initializes, so that condition is always true if it can be loaded.

I don't think your change is correct either. getValue() will never return computed values, so the result is the same as HEAD.

I don't think it is possible at all to do this, because even accessing the EntityReference property directly with get('entity') there simply is no API to detect whether it has been set or not. There is no public method to access $this->target and \Drupal\Core\Entity\Plugin\DataType\EntityReference::getTarget() always loads it if not set, that's the same as ->entity.

That's why \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase::prepareView + \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase::getEntitiesToView() use the magic _loaded property to track that.

So to change this, we'd first need to extend the underlying entity reference property to add a method to check if the refernce is loaded or not.

dieterholvoet’s picture

I don't think your change is correct either. getValue() will never return computed values, so the result is the same as HEAD.

No, that does seem to work. Because EntityReferenceItem::getValue() looks like this:

/**
 * {@inheritdoc}
 */
public function getValue() {
  $values = parent::getValue();
  // If there is an unsaved entity, return it as part of the field item values
  // to ensure idempotency of getValue() / setValue().
  if ($this->hasNewEntity()) {
    $values['entity'] = $this->entity;
  }
  return $values;
}

$this->entity also triggers EntityReference::getTarget(), but since isset($this->target) it doesn't trigger a new entity load and returns the existing entity object instead.

I think that means that the previous approach would have also worked, if we had just replaced $item->entity !== NULL with $item->hasNewEntity().

dieterholvoet’s picture

By the way, I just tested this with code like this:

$this->set('field_event_type', [EventType::create()]);
$types = $this->get('field_event_type')->referencedEntities();

$types contained the newly created, unsaved entity.

berdir’s picture

It works for *new* entities, but it doesn't work for existing, changed entities, which is what this issue is trying to fix. \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities() in HEAD already accounts for this, because it falls back to that if target_id is null. That's consistent with hasNewEntity(), which also explicitly checks for target_id === NULL, so it doesn't matter whether that's checked first or not.

Your code will also work in HEAD. What is the bug that you are trying to fix?

We can not solve this and keep multiloading without exposing something like isTargetLoaded() on the reference property.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

dieterholvoet changed the visibility of the branch 2826717-entityreferencefielditemlistreferencedentities-might-not to active.