Commit ca64740b authored by webchick's avatar webchick

Issue #1723892 by Berdir, Pancho, das-peter, fago: Support for revisions for...

Issue #1723892 by Berdir, Pancho, das-peter, fago: Support for revisions for entity save and delete operations.
parent bbdc5256
...@@ -7,6 +7,7 @@ Drupal 8.0, xxxx-xx-xx (development version) ...@@ -7,6 +7,7 @@ Drupal 8.0, xxxx-xx-xx (development version)
* Drupal now understands the concept of a "default" revision, tracked * Drupal now understands the concept of a "default" revision, tracked
independently from the latest revision, allowing for the creation of independently from the latest revision, allowing for the creation of
drafts while the current revision stays published. drafts while the current revision stays published.
* All entity types, not just nodes, now have support for revisions.
- Replaced the core routing system with one built on the Symfony2 framework. - Replaced the core routing system with one built on the Symfony2 framework.
- Configuration: - Configuration:
* Added a centralized file-based configuration system. * Added a centralized file-based configuration system.
......
...@@ -149,6 +149,18 @@ function entity_revision_load($entity_type, $revision_id) { ...@@ -149,6 +149,18 @@ function entity_revision_load($entity_type, $revision_id) {
return entity_get_controller($entity_type)->loadRevision($revision_id); return entity_get_controller($entity_type)->loadRevision($revision_id);
} }
/**
* Deletes a node revision.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.
* @param $revision_id
* The revision ID to delete.
*/
function entity_revision_delete($entity_type, $revision_id) {
entity_get_controller($entity_type)->deleteRevision($revision_id);
}
/** /**
* Loads an entity by UUID. * Loads an entity by UUID.
* *
......
...@@ -121,6 +121,13 @@ public function loadRevision($revision_id) { ...@@ -121,6 +121,13 @@ public function loadRevision($revision_id) {
return FALSE; return FALSE;
} }
/**
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::deleteRevision().
*/
public function deleteRevision($revision_id) {
return NULL;
}
/** /**
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties(). * Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties().
*/ */
......
...@@ -242,6 +242,23 @@ public function loadRevision($revision_id) { ...@@ -242,6 +242,23 @@ public function loadRevision($revision_id) {
return reset($queried_entities); return reset($queried_entities);
} }
/**
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::deleteRevision().
*/
public function deleteRevision($revision_id) {
if ($revision = $this->loadRevision($revision_id)) {
// Prevent deletion if this is the default revision.
if ($revision->isDefaultRevision()) {
throw new EntityStorageException('Default revision can not be deleted');
}
db_delete($this->revisionTable)
->condition($this->revisionKey, $revision->getRevisionId())
->execute();
$this->invokeHook('revision_delete', $revision);
}
}
/** /**
* Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties(). * Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties().
*/ */
...@@ -446,6 +463,13 @@ public function delete($ids) { ...@@ -446,6 +463,13 @@ public function delete($ids) {
db_delete($this->entityInfo['base table']) db_delete($this->entityInfo['base table'])
->condition($this->idKey, $ids, 'IN') ->condition($this->idKey, $ids, 'IN')
->execute(); ->execute();
if ($this->revisionKey) {
db_delete($this->revisionTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
// Reset the cache as soon as the changes have been applied. // Reset the cache as soon as the changes have been applied.
$this->resetCache($ids); $this->resetCache($ids);
...@@ -478,13 +502,26 @@ public function save(EntityInterface $entity) { ...@@ -478,13 +502,26 @@ public function save(EntityInterface $entity) {
$this->invokeHook('presave', $entity); $this->invokeHook('presave', $entity);
if (!$entity->isNew()) { if (!$entity->isNew()) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); if ($entity->isDefaultRevision()) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
}
else {
// @todo, should a different value be returned when saving an entity
// with $isDefaultRevision = FALSE?
$return = FALSE;
}
if ($this->revisionKey) {
$this->saveRevision($entity);
}
$this->resetCache(array($entity->id())); $this->resetCache(array($entity->id()));
$this->postSave($entity, TRUE); $this->postSave($entity, TRUE);
$this->invokeHook('update', $entity); $this->invokeHook('update', $entity);
} }
else { else {
$return = drupal_write_record($this->entityInfo['base table'], $entity); $return = drupal_write_record($this->entityInfo['base table'], $entity);
if ($this->revisionKey) {
$this->saveRevision($entity);
}
// Reset general caches, but keep caches specific to certain entities. // Reset general caches, but keep caches specific to certain entities.
$this->resetCache(array()); $this->resetCache(array());
...@@ -506,6 +543,43 @@ public function save(EntityInterface $entity) { ...@@ -506,6 +543,43 @@ public function save(EntityInterface $entity) {
} }
} }
/**
* Saves an entity revision.
*
* @param Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
protected function saveRevision(EntityInterface $entity) {
// Convert the entity into an array as it might not have the same properties
// as the entity, it is just a raw structure.
$record = (array) $entity;
// When saving a new revision, set any existing revision ID to NULL so as to
// ensure that a new revision will actually be created, then store the old
// revision ID in a separate property for use by hook implementations.
if ($entity->isNewRevision() && $record[$this->revisionKey]) {
$record[$this->revisionKey] = NULL;
}
$this->preSaveRevision($record, $entity);
if ($entity->isNewRevision()) {
drupal_write_record($this->revisionTable, $record);
if ($entity->isDefaultRevision()) {
db_update($this->entityInfo['base table'])
->fields(array($this->revisionKey => $record[$this->revisionKey]))
->condition($this->idKey, $entity->id())
->execute();
}
$entity->setNewRevision(FALSE);
}
else {
drupal_write_record($this->revisionTable, $record, $this->revisionKey);
}
// Make sure to update the new revision key for the entity.
$entity->{$this->revisionKey} = $record[$this->revisionKey];
}
/** /**
* Acts on an entity before the presave hook is invoked. * Acts on an entity before the presave hook is invoked.
* *
...@@ -539,6 +613,16 @@ protected function preDelete($entities) { } ...@@ -539,6 +613,16 @@ protected function preDelete($entities) { }
*/ */
protected function postDelete($entities) { } protected function postDelete($entities) { }
/**
* Act on a revision before being saved.
*
* @param array $record
* The revision array.
* @param Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
protected function preSaveRevision(array &$record, EntityInterface $entity) { }
/** /**
* Invokes a hook on behalf of the entity. * Invokes a hook on behalf of the entity.
* *
...@@ -548,7 +632,13 @@ protected function postDelete($entities) { } ...@@ -548,7 +632,13 @@ protected function postDelete($entities) { }
* The entity object. * The entity object.
*/ */
protected function invokeHook($hook, EntityInterface $entity) { protected function invokeHook($hook, EntityInterface $entity) {
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) { $function = 'field_attach_' . $hook;
// @todo: field_attach_delete_revision() is named the wrong way round,
// consider renaming it.
if ($function == 'field_attach_revision_delete') {
$function = 'field_attach_delete_revision';
}
if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
$function($this->entityType, $entity); $function($this->entityType, $entity);
} }
// Invoke the hook. // Invoke the hook.
......
...@@ -42,6 +42,13 @@ class Entity implements IteratorAggregate, EntityInterface { ...@@ -42,6 +42,13 @@ class Entity implements IteratorAggregate, EntityInterface {
*/ */
protected $enforceIsNew; protected $enforceIsNew;
/**
* Boolean indicating whether a new revision should be created on save.
*
* @var bool
*/
protected $newRevision = FALSE;
/** /**
* Indicates whether this is the default revision. * Indicates whether this is the default revision.
* *
...@@ -81,6 +88,14 @@ public function isNew() { ...@@ -81,6 +88,14 @@ public function isNew() {
return !empty($this->enforceIsNew) || !$this->id(); return !empty($this->enforceIsNew) || !$this->id();
} }
/**
* Implements EntityInterface::isNewRevision().
*/
public function isNewRevision() {
$info = $this->entityInfo();
return $this->newRevision || (!empty($info['entity keys']['revision']) && !$this->getRevisionId());
}
/** /**
* Implements EntityInterface::enforceIsNew(). * Implements EntityInterface::enforceIsNew().
*/ */
...@@ -88,6 +103,13 @@ public function enforceIsNew($value = TRUE) { ...@@ -88,6 +103,13 @@ public function enforceIsNew($value = TRUE) {
$this->enforceIsNew = $value; $this->enforceIsNew = $value;
} }
/**
* Implements EntityInterface::setNewRevision().
*/
public function setNewRevision($value = TRUE) {
$this->newRevision = $value;
}
/** /**
* Implements EntityInterface::entityType(). * Implements EntityInterface::entityType().
*/ */
......
...@@ -63,6 +63,26 @@ public function uuid(); ...@@ -63,6 +63,26 @@ public function uuid();
*/ */
public function isNew(); public function isNew();
/**
* Returns whether a new revision should be created on save.
*
* @return bool
* TRUE if a new revision should be created.
*
* @see Drupal\Core\Entity\EntityInterface::setNewRevision()
*/
public function isNewRevision();
/**
* Enforces an entity to be saved as a new revision.
*
* @param bool $value
* (optional) Whether a new revision should be saved.
*
* @see Drupal\Core\Entity\EntityInterface::isNewRevision()
*/
public function setNewRevision($value = TRUE);
/** /**
* Enforces an entity to be new. * Enforces an entity to be new.
* *
......
...@@ -59,6 +59,16 @@ public function load(array $ids = NULL); ...@@ -59,6 +59,16 @@ public function load(array $ids = NULL);
*/ */
public function loadRevision($revision_id); public function loadRevision($revision_id);
/**
* Delete a specific entity revision.
*
* A revision can only be deleted if it's not the currently active one.
*
* @param int $revision_id
* The revision id.
*/
public function deleteRevision($revision_id);
/** /**
* Load entities by their property values. * Load entities by their property values.
* *
......
...@@ -162,7 +162,7 @@ function book_admin_edit_submit($form, &$form_state) { ...@@ -162,7 +162,7 @@ function book_admin_edit_submit($form, &$form_state) {
$langcode = LANGUAGE_NOT_SPECIFIED; $langcode = LANGUAGE_NOT_SPECIFIED;
$node->title = $values['title']; $node->title = $values['title'];
$node->book['link_title'] = $values['title']; $node->book['link_title'] = $values['title'];
$node->revision = 1; $node->setNewRevision();
$node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title'])); $node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title']));
$node->save(); $node->save();
......
...@@ -927,7 +927,7 @@ function book_page_alter(&$page) { ...@@ -927,7 +927,7 @@ function book_page_alter(&$page) {
function book_node_presave(Node $node) { function book_node_presave(Node $node) {
// Always save a revision for non-administrators. // Always save a revision for non-administrators.
if (!empty($node->book['bid']) && !user_access('administer nodes')) { if (!empty($node->book['bid']) && !user_access('administer nodes')) {
$node->revision = 1; $node->setNewRevision();
} }
// Make sure a new node gets a new menu link. // Make sure a new node gets a new menu link.
if (empty($node->nid)) { if (empty($node->nid)) {
......
...@@ -48,13 +48,15 @@ public static function getInfo() { ...@@ -48,13 +48,15 @@ public static function getInfo() {
protected function convertToPartialEntities($entities, $field_name) { protected function convertToPartialEntities($entities, $field_name) {
$partial_entities = array(); $partial_entities = array();
foreach ($entities as $id => $entity) { foreach ($entities as $id => $entity) {
// Re-create the entity with only the required keys, remove label as that // Re-create the entity to match what is expected
// is not present when using _field_create_entity_from_ids(). // _field_create_entity_from_ids().
$partial_entities[$id] = field_test_create_entity($entity->ftid, $entity->ftvid, $entity->fttype, $entity->ftlabel); $ids = (object) array(
// Remove the label and set enforceIsNew to NULL to make sure that the 'entity_id' => $entity->ftid,
// entity classes match the actual arguments. 'revision_id' => $entity->ftvid,
unset($partial_entities[$id]->ftlabel); 'bundle' => $entity->fttype,
$partial_entities[$id]->enforceIsNew(NULL); 'entity_type' => 'test_entity',
);
$partial_entities[$id] = _field_create_entity_from_ids($ids);
$partial_entities[$id]->$field_name = $entity->$field_name; $partial_entities[$id]->$field_name = $entity->$field_name;
} }
return $partial_entities; return $partial_entities;
......
...@@ -24,8 +24,8 @@ public static function getInfo() { ...@@ -24,8 +24,8 @@ public static function getInfo() {
*/ */
function testSelectListDynamic() { function testSelectListDynamic() {
// Create an entity. // Create an entity.
$this->entity->is_new = TRUE; $this->entity->save();
field_test_entity_save($this->entity);
// Create a web user. // Create a web user.
$web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
$this->drupalLogin($web_user); $this->drupalLogin($web_user);
......
...@@ -28,5 +28,10 @@ function options_test_allowed_values_callback($field, $instance, $entity_type, $ ...@@ -28,5 +28,10 @@ function options_test_allowed_values_callback($field, $instance, $entity_type, $
function options_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) { function options_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) {
$cacheable = FALSE; $cacheable = FALSE;
// We need the values of the entity as keys. // We need the values of the entity as keys.
return drupal_map_assoc(array_merge(array($entity->ftlabel), array($entity->id(), $entity->getRevisionId(), $entity->bundle()))); return drupal_map_assoc(array(
$entity->ftlabel,
$entity->id(),
$entity->getRevisionId(),
$entity->bundle(),
));
} }
...@@ -237,6 +237,8 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l ...@@ -237,6 +237,8 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l
} }
if (isset($vid)) { if (isset($vid)) {
$entity->ftvid = $vid; $entity->ftvid = $vid;
// Flag to make sure that the provided vid is used for a new revision.
$entity->use_provided_revision_id = $vid;
} }
$entity->fttype = $bundle; $entity->fttype = $bundle;
...@@ -244,6 +246,7 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l ...@@ -244,6 +246,7 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l
$entity->ftlabel = $label; $entity->ftlabel = $label;
// Make sure the entity will saved even if a primary key is provided. // Make sure the entity will saved even if a primary key is provided.
$entity->enforceIsNew(); $entity->enforceIsNew();
$entity->setNewRevision();
return $entity; return $entity;
} }
...@@ -293,7 +296,6 @@ function field_test_entity_edit(TestEntity $entity) { ...@@ -293,7 +296,6 @@ function field_test_entity_edit(TestEntity $entity) {
return entity_get_form($entity); return entity_get_form($entity);
} }
/** /**
* Form combining two separate entities. * Form combining two separate entities.
*/ */
......
...@@ -35,6 +35,13 @@ class TestEntity extends Entity { ...@@ -35,6 +35,13 @@ class TestEntity extends Entity {
*/ */
public $fttype; public $fttype;
/**
* Label property
*
* @var string
*/
public $ftlabel;
/** /**
* Overrides Drupal\Core\Entity\Entity::id(). * Overrides Drupal\Core\Entity\Entity::id().
*/ */
......
...@@ -16,36 +16,12 @@ ...@@ -16,36 +16,12 @@
class TestEntityController extends DatabaseStorageController { class TestEntityController extends DatabaseStorageController {
/** /**
* Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision().
*/ */
public function preSave(EntityInterface $entity) { public function preSaveRevision(array &$record, EntityInterface $entity) {
// Prepare for a new revision. // Allow for predefined revision ids.
if (!$entity->isNew() && !empty($entity->revision)) { if (!empty($record['use_provided_revision_id'])) {
$entity->old_ftvid = $entity->ftvid; $record['ftvid'] = $record['use_provided_revision_id'];
$entity->ftvid = NULL;
}
}
/**
* Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
*/
public function postSave(EntityInterface $entity, $update) {
// Only the test_entity entity type has revisions.
if ($entity->entityType() == 'test_entity') {
$update_entity = TRUE;
if (!$update || !empty($entity->revision)) {
drupal_write_record('test_entity_revision', $entity);
}
else {
drupal_write_record('test_entity_revision', $entity, 'ftvid');
$update_entity = FALSE;
}
if ($update_entity) {
db_update('test_entity')
->fields(array('ftvid' => $entity->ftvid))
->condition('ftid', $entity->ftid)
->execute();
}
} }
} }
......
...@@ -231,7 +231,7 @@ function file_field_insert($entity_type, $entity, $field, $instance, $langcode, ...@@ -231,7 +231,7 @@ function file_field_insert($entity_type, $entity, $field, $instance, $langcode,
function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
// On new revisions, all files are considered to be a new usage and no // On new revisions, all files are considered to be a new usage and no
// deletion of previous file usages are necessary. // deletion of previous file usages are necessary.
if (!empty($entity->revision)) { if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
foreach ($items as $item) { foreach ($items as $item) {
file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id()); file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id());
} }
......
...@@ -346,7 +346,9 @@ function forum_node_presave(Node $node) { ...@@ -346,7 +346,9 @@ function forum_node_presave(Node $node) {
*/ */
function forum_node_update(Node $node) { function forum_node_update(Node $node) {
if (_forum_node_check_node_type($node)) { if (_forum_node_check_node_type($node)) {
if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) { // If this is not a new revision and does exist, update the forum record,
// otherwise insert a new one.
if ($node->getRevisionId() == $node->original->getRevisionId() && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
if (!empty($node->forum_tid)) { if (!empty($node->forum_tid)) {
db_update('forum') db_update('forum')
->fields(array('tid' => $node->forum_tid)) ->fields(array('tid' => $node->forum_tid))
......
...@@ -44,7 +44,7 @@ protected function prepareEntity(EntityInterface $node) { ...@@ -44,7 +44,7 @@ protected function prepareEntity(EntityInterface $node) {
$node->log = NULL; $node->log = NULL;
} }
// Always use the default revision setting. // Always use the default revision setting.
$node->revision = in_array('revision', $node_options); $node->setNewRevision(in_array('revision', $node_options));
node_invoke($node, 'prepare'); node_invoke($node, 'prepare');
module_invoke_all('node_prepare', $node); module_invoke_all('node_prepare', $node);
...@@ -117,8 +117,8 @@ public function form(array $form, array &$form_state, EntityInterface $node) { ...@@ -117,8 +117,8 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
'#type' => 'fieldset', '#type' => 'fieldset',
'#title' => t('Revision information'), '#title' => t('Revision information'),
'#collapsible' => TRUE, '#collapsible' => TRUE,
// Collapsed by default when "Create new revision" is unchecked // Collapsed by default when "Create new revision" is unchecked.
'#collapsed' => !$node->revision, '#collapsed' => !$node->isNewRevision(),
'#group' => 'additional_settings', '#group' => 'additional_settings',
'#attributes' => array( '#attributes' => array(
'class' => array('node-form-revision-information'), 'class' => array('node-form-revision-information'),
...@@ -127,20 +127,20 @@ public function form(array $form, array &$form_state, EntityInterface $node) { ...@@ -127,20 +127,20 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
'js' => array(drupal_get_path('module', 'node') . '/node.js'), 'js' => array(drupal_get_path('module', 'node') . '/node.js'),
), ),
'#weight' => 20, '#weight' => 20,
'#access' => $node->revision || user_access('administer nodes'), '#access' => $node->isNewRevision() || user_access('administer nodes'),
); );
$form['revision_information']['revision'] = array( $form['revision_information']['revision'] = array(
'#type' => 'checkbox', '#type' => 'checkbox',
'#title' => t('Create new revision'), '#title' => t('Create new revision'),
'#default_value' => $node->revision, '#default_value' => $node->isNewRevision(),
'#access' => user_access('administer nodes'), '#access' => user_access('administer nodes'),
); );
// Check the revision log checkbox when the log textarea is filled in. // Check the revision log checkbox when the log textarea is filled in.
// This must not happen if "Create new revision" is enabled by default, // This must not happen if "Create new revision" is enabled by default,
// since the state would auto-disable the checkbox otherwise. // since the state would auto-disable the checkbox otherwise.
if (!$node->revision) { if (!$node->isNewRevision()) {
$form['revision_information']['revision']['#states'] = array( $form['revision_information']['revision']['#states'] = array(
'checked' => array( 'checked' => array(
'textarea[name="log"]' => array('empty' => FALSE), 'textarea[name="log"]' => array('empty' => FALSE),
...@@ -321,6 +321,11 @@ public function submit(array $form, array &$form_state) { ...@@ -321,6 +321,11 @@ public function submit(array $form, array &$form_state) {
// Build the node object from the submitted values. // Build the node object from the submitted values.
$node = parent::submit($form, $form_state); $node = parent::submit($form, $form_state);
// Save as a new revision if requested to do so.
if (!empty($form_state['values']['revision'])) {
$node->setNewRevision();
}
node_submit($node); node_submit($node);
foreach (module_implements('node_submit') as $module) { foreach (module_implements('node_submit') as $module) {
$function = $module . '_node_submit'; $function = $module . '_node_submit';
......
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
use Drupal\Core\Entity\DatabaseStorageController; use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Exception;
/** /**
* Controller class for nodes. * Controller class for nodes.
...@@ -34,135 +32,6 @@ public function create(array $values) { ...@@ -34,135 +32,6 @@ public function create(array $values) {
return $node; return $node;
} }
/**
* Overrides Drupal\Core\Entity\DatabaseStorageController::delete().
*/
public function delete($ids) {
$entities = $ids ? $this->load($ids) : FALSE;
if (!$entities) {
// If no IDs or invalid IDs were passed, do nothing.
return;
}
$transaction = db_transaction();
try {
$this->preDelete($entities);
foreach ($entities as $id => $entity) {
$this->invokeHook('predelete', $entity);
}
$ids = array_keys($entities);
db_delete($this->entityInfo['base table'])
->condition($this->idKey, $ids, 'IN')
->execute();
if ($this->revisionKey) {
db_delete($this->revisionTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
$this->postDelete($entities);