$entity->original gets stale between updates

Created on 20 November 2013, about 11 years ago
Updated 12 April 2023, over 1 year ago

Problem/Motivation

In contrib it is common for workflows to be implemented by resaving the entity in a hook_entity_update() or a matching Rule.
So if my commerce order needs to go through 4 different statuses, and can't skip them by just doing hook_entity_presave(), it is going to do do a status change and a save as the last hook_entity_update() operation / rule.

The code that initializes $entity->original is contained in EntityStorageBase::doPreSave(), the method called before hook_entity_update() implementations are invoked, which contains the following code.

// Load the original entity, if any.
if ($id_exists && !isset($entity->original)) {
  $entity->original = $this->loadUnchanged($id);
}

This prevents $entity->original from being reloaded, so when I react on a fourth status change, $entity->original->status is still pointing at the first one. Ideally workflows like this would be handled in events / hooks outside of the save pipeline, but Drupal is not currently discouraging this practice.

Steps to reproduce

use Drupal\node\Entity\Node;

$node = Node::create(['type' => 'page']);
$node->set('title', 'Drupal 9 rocks!');
$node->save();
$node->set('title', 'Drupal 10 rocks!');
$node->save();

/**
 * Implements hook_node_update().
 */
function mymodule_node_update(Node $node): void {
  // The node has been created with the title 'Drupal 9 rocks!'.
  // Then we update it to say "Drupal 10 rocks!".
  // This hook catches that update, and changes it to "Drupal 11 rocks!".
  // This is a simple example that could be replaced with a presave() hook,
  // but in the case of workflow changes, every status needs to be cycled
  // through, no skipping allowed.
  if ($node->title == 'Drupal 10 rocks!') {
    $node->set('title', 'Drupal 11 rocks!');
    $node->save();
    return;
  }

  // On second update, check the value of $node->original.
  if ($node->title == 'Drupal 11 rocks!') {
    if ($node->original->title == 'Drupal 10 rocks!') {
      dump('The node has the correct original value');
    }
    if ($node->original->title == 'Drupal 9 rocks!') {
      dump('The node has an incorrect original value');
    }
  }
}

Proposed resolution

Stop doing the isset check (it's premature optimization anyway), and then the concrete bug is fixed.

// Load the original entity, if any.
if ($id_exists) {
  $entity->original = $this->loadUnchanged($id);
}

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

πŸ› Bug report
Status

Needs work

Version

10.1 ✨

Component
EntityΒ  β†’

Last updated about 11 hours ago

Created by

πŸ‡·πŸ‡ΈSerbia bojanz

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024