Commit d7a9d44e authored by catch's avatar catch

Issue #2020895 by yched, pcambra, swentel: Move save() / delete() logic in...

Issue #2020895 by yched, pcambra, swentel: Move save() / delete() logic in Field / FieldInstance to [pre|post]Save(), [pre|post]Delete().
parent dd5dcd1b
......@@ -1106,7 +1106,6 @@ public function onFieldUpdate(FieldInterface $field) {
*/
public function onFieldDelete(FieldInterface $field) {
// Mark all data associated with the field for deletion.
$field->deleted = FALSE;
$table = static::_fieldTableName($field);
$revision_table = static::_fieldRevisionTableName($field);
$this->database->update($table)
......@@ -1115,9 +1114,10 @@ public function onFieldDelete(FieldInterface $field) {
// Move the table to a unique name while the table contents are being
// deleted.
$field->deleted = TRUE;
$new_table = static::_fieldTableName($field);
$revision_new_table = static::_fieldRevisionTableName($field);
$deleted_field = clone $field;
$deleted_field->deleted = TRUE;
$new_table = static::_fieldTableName($deleted_field);
$revision_new_table = static::_fieldRevisionTableName($deleted_field);
$this->database->schema()->renameTable($table, $new_table);
$this->database->schema()->renameTable($revision_table, $revision_new_table);
}
......
......@@ -10,6 +10,7 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\field\FieldException;
use Drupal\field\FieldInterface;
......@@ -270,42 +271,35 @@ public function getExportProperties() {
}
/**
* Overrides \Drupal\Core\Entity\Entity::save().
*
* @return int
* Either SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
* @throws \Drupal\field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
*/
public function save() {
public function preSave(EntityStorageControllerInterface $storage_controller) {
// Clear the derived data about the field.
unset($this->schema);
if ($this->isNew()) {
return $this->saveNew();
return $this->preSaveNew($storage_controller);
}
else {
return $this->saveUpdated();
return $this->preSaveUpdated($storage_controller);
}
}
/**
* Saves a new field definition.
* Prepares saving a new field definition.
*
* @return int
* SAVED_NEW if the definition was saved.
* @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
* The entity storage controller.
*
* @throws \Drupal\field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
* @throws \Drupal\field\FieldException If the field definition is invalid.
*/
protected function saveNew() {
protected function preSaveNew(EntityStorageControllerInterface $storage_controller) {
$entity_manager = \Drupal::entityManager();
$storage_controller = $entity_manager->getStorageController($this->entityType);
// Assign the ID.
$this->id = $this->id();
......@@ -353,100 +347,103 @@ protected function saveNew() {
// Notify the entity storage controller.
$entity_manager->getStorageController($this->entity_type)->onFieldCreate($this);
// Save the configuration.
$result = parent::save();
field_cache_clear();
return $result;
}
/**
* Saves an updated field definition.
*
* @return int
* SAVED_UPDATED if the definition was saved.
* Prepares saving an updated field definition.
*
* @throws \Drupal\field\FieldException
* If the field definition is invalid.
* @throws \Drupal\Core\Entity\EntityStorageException
* In case of failures at the configuration storage level.
* @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
* The entity storage controller.
*/
protected function saveUpdated() {
protected function preSaveUpdated(EntityStorageControllerInterface $storage_controller) {
$module_handler = \Drupal::moduleHandler();
$entity_manager = \Drupal::entityManager();
$storage_controller = $entity_manager->getStorageController($this->entityType);
$original = $storage_controller->loadUnchanged($this->id());
$this->original = $original;
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
// Some updates are always disallowed.
if ($this->type != $original->type) {
if ($this->type != $this->original->type) {
throw new FieldException("Cannot change an existing field's type.");
}
if ($this->entity_type != $original->entity_type) {
if ($this->entity_type != $this->original->entity_type) {
throw new FieldException("Cannot change an existing field's entity_type.");
}
// Make sure all settings are present, so that a complete field definition
// is saved. This allows calling code to perform partial updates on field
// objects.
$this->settings += $original->settings;
// Make sure all settings are present, so that a complete field
// definition is passed to the various hooks and written to config.
$this->settings += $field_type_manager->getDefaultSettings($this->type);
// See if any module forbids the update by throwing an exception. This
// invokes hook_field_update_forbid().
$module_handler->invokeAll('field_update_forbid', array($this, $original));
$module_handler->invokeAll('field_update_forbid', array($this, $this->original));
// Notify the storage controller. The controller can reject the definition
// update as invalid by raising an exception, which stops execution before
// the definition is written to config.
$entity_manager->getStorageController($this->entity_type)->onFieldUpdate($this, $original);
$entity_manager->getStorageController($this->entity_type)->onFieldUpdate($this);
}
// Save the configuration.
$result = parent::save();
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
// Clear the cache.
field_cache_clear();
return $result;
}
/**
* {@inheritdoc}
*/
public function delete() {
if (!$this->deleted) {
$instance_controller = \Drupal::entityManager()->getStorageController('field_instance');
$state = \Drupal::state();
// Delete all non-deleted instances.
$instance_ids = array();
foreach ($this->getBundles() as $bundle) {
$instance_ids[] = "{$this->entity_type}.$bundle.{$this->name}";
public static function preDelete(EntityStorageControllerInterface $storage_controller, array $fields) {
$state = \Drupal::state();
$instance_controller = \Drupal::entityManager()->getStorageController('field_instance');
// Delete instances first. Note: when deleting a field through
// FieldInstance::postDelete(), the instances have been deleted already, so
// no instances will be found here.
$instance_ids = array();
foreach ($fields as $field) {
if (!$field->deleted) {
foreach ($field->getBundles() as $bundle) {
$instance_ids[] = "{$field->entity_type}.$bundle.{$field->name}";
}
}
foreach ($instance_controller->loadMultiple($instance_ids) as $instance) {
// By default, FieldInstance::delete() will automatically try to delete
// a field definition when it is deleting the last instance of the
// field. Since the whole field is being deleted here, pass FALSE as
// the $field_cleanup parameter to prevent a loop.
$instance->delete(FALSE);
}
if ($instance_ids) {
$instances = $instance_controller->loadMultiple($instance_ids);
// Tag the objects to preserve recursive deletion of the field.
foreach ($instances as $instance) {
$instance->noFieldDelete = TRUE;
}
$instance_controller->delete($instances);
}
\Drupal::entityManager()->getStorageController($this->entity_type)->onFieldDelete($this);
// Delete the configuration of this field and save the field configuration
// in the key_value table so we can use it later during
// field_purge_batch(). This makes sure a new field can be created
// immediately with the same name.
$deleted_fields = $state->get('field.field.deleted') ?: array();
$config = $this->getExportProperties();
$config['deleted'] = TRUE;
$deleted_fields[$this->uuid] = $config;
$state->set('field.field.deleted', $deleted_fields);
parent::delete();
// Keep the field definitions in the state storage so we can use them later
// during field_purge_batch().
$deleted_fields = $state->get('field.field.deleted') ?: array();
foreach ($fields as $field) {
if (!$field->deleted) {
$config = $field->getExportProperties();
$config['deleted'] = TRUE;
$config['bundles'] = $field->getBundles();
$deleted_fields[$field->uuid] = $config;
}
}
$state->set('field.field.deleted', $deleted_fields);
}
// Clear the cache.
field_cache_clear();
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageControllerInterface $storage_controller, array $fields) {
// Notify the storage.
foreach ($fields as $field) {
if (!$field->deleted) {
\Drupal::entityManager()->getStorageController($field->entity_type)->onFieldDelete($field);
}
}
// Clear the cache.
field_cache_clear();
}
/**
......
......@@ -174,13 +174,46 @@ function testDeleteFieldInstance() {
// Make sure the other field instance is not deleted.
$another_instance = field_read_instance('entity_test', $another_instance_definition['field_name'], $another_instance_definition['bundle']);
$this->assertTrue(!empty($another_instance) && empty($another_instance->deleted), 'A non-deleted field instance is not marked for deletion.');
}
// Make sure the field is deleted when its last instance is deleted.
$another_instance->delete();
$deleted_fields = \Drupal::state()->get('field.field.deleted');
$this->assertTrue(isset($deleted_fields[$another_instance->field_uuid]), 'A deleted field is marked for deletion.');
$field = field_read_field($another_instance->entity_type, $another_instance->getFieldName());
$this->assertFalse($field, 'The field marked to be deleted is not found anymore in the configuration.');
/**
* Tests the cross deletion behavior between fields and instances.
*/
function testDeleteFieldInstanceCrossDeletion() {
$instance_definition_2 = $this->instance_definition;
$instance_definition_2['bundle'] .= '_another_bundle';
// Check that deletion of a field deletes its instances.
$field = $this->field;
entity_create('field_instance', $this->instance_definition)->save();
entity_create('field_instance', $instance_definition_2)->save();
$field->delete();
$this->assertFalse(field_info_instance('entity_test', $this->instance_definition['bundle'], $field->name));
$this->assertFalse(field_info_instance('entity_test', $instance_definition_2['bundle'], $field->name));
// Chack that deletion of the last instance deletes the field.
$field = entity_create('field_entity', $this->field_definition);
$field->save();
$instance = entity_create('field_instance', $this->instance_definition);
$instance->save();
$instance_2 = entity_create('field_instance', $instance_definition_2);
$instance_2->save();
$instance->delete();
$this->assertTrue(field_info_field('entity_test', $field->name));
$instance_2->delete();
$this->assertFalse(field_info_field('entity_test', $field->name));
// Check that deletion of all instances of the same field simultaneously
// deletes the field.
$field = entity_create('field_entity', $this->field_definition);
$field->save();
$instance = entity_create('field_instance', $this->instance_definition);
$instance->save();
$instance_2 = entity_create('field_instance', $instance_definition_2);
$instance_2->save();
$instance_controller = $this->container->get('entity.manager')->getStorageController('field_instance');
$instance_controller->delete(array($instance, $instance_2));
$this->assertFalse(field_info_field('entity_test', $field->name));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment