Problem/Motivation

As discovered by acbramley in #2700197: Transition Event $state_before is not correct when transitioning a non default revision for Workbench Moderation, $entity->original (as found in hook_entity_presave and similar) is not, in fact, the original entity. It is the current default revision. In core, under normal circumstances, that is always the same as the original entity. However, there is nothing that guarantees it is. In fact, if you install Workbench Moderation (or any other module that makes use of forward revisions), it will very often NOT be the case. That results in $entity->original being wrong in many cases.

On further investigation, I believe the issue is in EntityStorageBase::loadUnchanged(), which does a fresh load from the database(!) using ::load(), which by design looks for the default revision only. What we need instead is a loadLatestRevision() method, which will load the latest revision regardless of what it's default status is.

Proposed resolution

In Workbench Moderation, I have this routine for doing so:

  public function getLatestRevision($entity_type_id, $entity_id) {
    if ($latest_revision_id = $this->getLatestRevisionId($entity_type_id, $entity_id)) {
      return $this->entityTypeManager->getStorage($entity_type_id)->loadRevision($latest_revision_id);
    }
  }

  public function getLatestRevisionId($entity_type_id, $entity_id) {
    if ($storage = $this->entityTypeManager->getStorage($entity_type_id)) {
      $revision_ids = $storage->getQuery()
        ->allRevisions()
        ->condition($this->entityTypeManager->getDefinition($entity_type_id)->getKey('id'), $entity_id)
        ->sort($this->entityTypeManager->getDefinition($entity_type_id)->getKey('revision'), 'DESC')
        ->range(0, 1)
        ->execute();
      if ($revision_ids) {
        $revision_id = array_keys($revision_ids)[0];
        return $revision_id;
      }
    }
  }

Which I don't really like, but it's the best I could do from the outside. We could move that logic, more or less, into EntityStorageBase, and then use getLatestRevision() instead of loadUnchanged() (which maybe should be deprecated, per #2226037: Change signature of Entity::loadUnchanged() to accept an EntityInterface instead of random ID). I'm not sure what the entity cache implications are of this approach, however.

Alternatively, I'm open to suggestions for alternate implementations from the Entity API maintainers. :-) As long as in the end we end up with a useful getLatestRevision() method, which we then call from EntityStorageBase::doPresSave() and others, that would fix the bug as well as let me remove some now-redundant code from Workbench Moderation, which would be a nice bonus.

Remaining tasks

Confirm that the above is the best approach, then implement it. (I can do the latter if a maintainer can do the former.)

User interface changes

None.

API changes

New API method, maybe.

Comments

Crell created an issue. See original summary.

Crell’s picture

Crell’s picture

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

lightguardjp’s picture

Is this being addressed by anything?

dawehner’s picture

We need to keep in mind the potential work of workspaces ... in a workspace the concept of the latest revision would need to be in the context of the current workspace. Not sure this would really block this issue, I'm just making a general comment. It could be the case that workspaces are taken into account automatically in the background.

Berdir’s picture

I think this is a duplicate of #2833084: $entity->original doesn't adequately address intentions when saving a revision and other related issues. We've been working on this problem space for a while now, see also #2809227: Store revision id in originalRevisionId property to use after revision id is updated.

Berdir’s picture

Status: Active » Closed (duplicate)

No objections, then so be it :)