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
Comment #2
Crell CreditAttribution: Crell at Palantir.net commentedComment #3
Crell CreditAttribution: Crell at Palantir.net commentedAdding links, and this comment because d.o won't let you add links without a comment body... (sigh)
Comment #5
lightguardjp CreditAttribution: lightguardjp commentedIs this being addressed by anything?
Comment #6
dawehnerWe 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.
Comment #7
BerdirI 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.
Comment #8
BerdirNo objections, then so be it :)