Not re-opening #1253194: fields are deleted when reverting exported bundle entities given its age, but I noticed something that went into Entity API in that patch.

  1. Suppose I have a feature "Feature X" with 2 exported bundle entities: "Entity A" and "Entity B"
  2. I override Entity A, while deleting Entity B from the Feature X
  3. Feature X now shows as overridden for Entity A and doesn't show entity B at all
  4. I choose to revert Feature X, which triggers a delete for Entity A
  5. EntityAPIControllerExportable::delete() triggers EntityAPIController::delete() for Entity A
  6. EntityAPIController::delete() invokes EntityAPIControllerExportable::invoke() which correctly sets $is_revert to TRUE, nothing happens to Entity A's fields
  7. EntityAPIControllerExportable::delete() runs further and notices Entity A had ENTITY_IN_CODE and triggers entity_defaults_rebuild()
  8. _entity_defaults_rebuild() figures Entity B used to live in code, but no longer does, so deletes it because it wasn't overridden
  9. EntityAPIControllerExportable::delete() triggers EntityAPIController::delete() for Entity B
  10. EntityAPIController::delete() invokes EntityAPIControllerExportable::invoke() which incorrectly sets $is_revert to TRUE, nothing happens to Entity B's fields

See below why this happens (snippet from EntityAPIControllerExportable::invoke()):

    if ($hook == 'delete') {
      // To ease figuring out whether this is a revert, make sure that the
      // entity status is updated in case the providing module has been
      // disabled.

      // THE FEATURE STILL EXISTS, SO ITS BUNDLE ENTITIES ARE NOT
      // BEING FLAGGED AS ENTITY_CUSTOM, CAUSING $is_revert TO BE SET TO TRUE.
      if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
        $entity->{$this->statusKey} = ENTITY_CUSTOM;
      }
      // THIS LEADS ENTITY API TO THINK THEY ARE ALL BEING REVERTED.
      $is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
    }

This is something I ran into after a long, hard review of Entity API's revert handling (as I needed to know the ins and outs for the Group module). I chose not to write a patch yet for the following reasons:

  • We could fix this in EntityAPIControllerExportable::invoke() and potentially keep other places where we need an $is_revert flag in the dark
  • Or we could adapt EntityDefaultUIController::applyOperation() and EntityDefaultFeaturesController::revert() to set an $is_revert flag on the entity before deleting it and check for that flag from now on.

Perhaps I'm stating the obvious, but setting an $is_revert flag would persist through all delete hooks until EntityAPIController::load() is called after EntityAPIController::delete() has run. This should be fine, really, and it wouldn't break backwards compatibility.

I'd love to hear your thoughts on this, Wolfgang.

Comments

kristiaanvandeneynde’s picture

This doesn't seem related at first glance. If anything, this issue and the duplicate I somehow created at #2309777: Potential data corruption in EntityAPIControllerExportable are about how Entity API incorrectly sets $is_revert, leading to orphaned field tables.

How this would trigger another entity_delete() is unclear to me. Have you checked your hook_entity_delete() and hook_ENTITY_delete() implementations?

kristiaanvandeneynde’s picture

Status: Active » Closed (duplicate)
Related issues: +#2309777: Potential data corruption in EntityAPIControllerExportable

Also...