Commit 271134ee authored by catch's avatar catch

Issue #2884202 by Berdir, amateescu, phenaproxima: Non-persistent fields...

Issue #2884202 by Berdir, amateescu, phenaproxima: Non-persistent fields become unpurgeable zombies without their target entity type
parent bf31e8cb
......@@ -3,6 +3,7 @@
namespace Drupal\field\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityStorageInterface;
use Drupal\Core\Field\FieldConfigBase;
use Drupal\Core\Field\FieldException;
use Drupal\field\FieldStorageConfigInterface;
......@@ -189,13 +190,19 @@ public function calculateDependencies() {
*/
public static function preDelete(EntityStorageInterface $storage, array $fields) {
$state = \Drupal::state();
$entity_type_manager = \Drupal::entityTypeManager();
parent::preDelete($storage, $fields);
// 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') ?: [];
/** @var \Drupal\field\FieldConfigInterface $field */
foreach ($fields as $field) {
if (!$field->deleted) {
// Only mark a field for purging if there is data. Otherwise, just remove
// it.
$target_entity_storage = $entity_type_manager->getStorage($field->getTargetEntityTypeId());
if (!$field->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field->getFieldStorageDefinition(), TRUE)) {
$config = $field->toArray();
$config['deleted'] = TRUE;
$config['field_storage_uuid'] = $field->getFieldStorageDefinition()->uuid();
......
......@@ -6,6 +6,7 @@
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\FieldableEntityStorageInterface;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
......@@ -409,8 +410,12 @@ public static function preDelete(EntityStorageInterface $storage, array $field_s
// Keep the field definitions in the state storage so we can use them later
// during field_purge_batch().
$deleted_storages = $state->get('field.storage.deleted') ?: [];
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
foreach ($field_storages as $field_storage) {
if (!$field_storage->deleted) {
// Only mark a field for purging if there is data. Otherwise, just remove
// it.
$target_entity_storage = \Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId());
if (!$field_storage->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field_storage, TRUE)) {
$config = $field_storage->toArray();
$config['deleted'] = TRUE;
$config['bundles'] = $field_storage->getBundles();
......
......@@ -269,12 +269,11 @@ public function testUpdateField() {
}
/**
* Test the deletion of a field.
* Test the deletion of a field with no data.
*/
public function testDeleteField() {
// TODO: Test deletion of the data stored in the field also.
// Need to check that data for a 'deleted' field / storage doesn't get loaded
// Need to check data marked deleted is cleaned on cron (not implemented yet...)
public function testDeleteFieldNoData() {
// Deleting and purging fields with data is tested in
// \Drupal\Tests\field\Kernel\BulkDeleteTest.
// Create two fields for the same field storage so we can test that only one
// is deleted.
......@@ -289,17 +288,18 @@ public function testDeleteField() {
$this->assertTrue(!empty($field) && empty($field->deleted), 'A new field is not marked for deletion.');
$field->delete();
// Make sure the field is marked as deleted when it is specifically loaded.
$field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]));
$this->assertTrue($field->isDeleted(), 'A deleted field is marked for deletion.');
// Make sure the field was deleted without being marked for purging as there
// was no data.
$fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]);
$this->assertEquals(0, count($fields), 'A deleted field is marked for deletion.');
// Try to load the field normally and make sure it does not show up.
$field = FieldConfig::load('entity_test.' . '.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
$this->assertTrue(empty($field), 'A deleted field is not loaded by default.');
$this->assertTrue(empty($field), 'Field was deleted');
// Make sure the other field is not deleted.
$another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
$this->assertTrue(!empty($another_field) && empty($another_field->deleted), 'A non-deleted field is not marked for deletion.');
$this->assertTrue(!empty($another_field) && !$another_field->isDeleted(), 'A non-deleted field is not marked for deletion.');
}
/**
......
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\field\Kernel;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
......@@ -50,6 +51,14 @@ public function testImportDelete() {
$field_config_name_2a = "field.field.$field_id_2a";
$field_config_name_2b = "field.field.$field_id_2b";
// Create an entity with data in the first field to make sure that field
// needs to be purged.
$entity_test = EntityTest::create([
'type' => 'entity_test',
]);
$entity_test->set($field_name, 'test data');
$entity_test->save();
// Create a second bundle for the 'Entity test' entity type.
entity_test_create_bundle('test_bundle');
......@@ -97,10 +106,10 @@ public function testImportDelete() {
$this->assertIdentical($active->listAll($field_config_name_2a), []);
$this->assertIdentical($active->listAll($field_config_name_2b), []);
// Check that the storage definition is preserved in state.
// Check that only the first storage definition is preserved in state.
$deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: [];
$this->assertTrue(isset($deleted_storages[$field_storage_uuid]));
$this->assertTrue(isset($deleted_storages[$field_storage_uuid_2]));
$this->assertFalse(isset($deleted_storages[$field_storage_uuid_2]));
// Purge field data, and check that the storage definition has been
// completely removed once the data is purged.
......
......@@ -284,8 +284,9 @@ public function testIndexes() {
/**
* Test the deletion of a field storage.
*/
public function testDelete() {
// TODO: Also test deletion of the data stored in the field ?
public function testDeleteNoData() {
// Deleting and purging field storages with data is tested in
// \Drupal\Tests\field\Kernel\BulkDeleteTest.
// Create two fields (so we can test that only one is deleted).
$field_storage_definition = [
......@@ -317,23 +318,22 @@ public function testDelete() {
$this->assertTrue(!empty($field_storage) && !$field_storage->isDeleted(), 'A new storage is not marked for deletion.');
FieldStorageConfig::loadByName('entity_test', $field_storage_definition['field_name'])->delete();
// Make sure that the field is marked as deleted when it is specifically
// loaded.
$field_storage = current(entity_load_multiple_by_properties('field_storage_config', ['field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE]));
$this->assertTrue($field_storage->isDeleted(), 'A deleted storage is marked for deletion.');
// Make sure that the field storage is deleted as it had no data.
$field_storages = entity_load_multiple_by_properties('field_storage_config', ['field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE]);
$this->assertEquals(0, count($field_storages), 'Field storage was deleted');
// Make sure that this field is marked as deleted when it is
// specifically loaded.
$field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $field_definition['field_name'], 'bundle' => $field_definition['bundle'], 'include_deleted' => TRUE]));
$this->assertTrue($field->isDeleted(), 'A field whose storage was deleted is marked for deletion.');
$fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $field_definition['field_name'], 'bundle' => $field_definition['bundle'], 'include_deleted' => TRUE]);
$this->assertEquals(0, count($fields), 'Field storage was deleted');
// Try to load the storage normally and make sure it does not show up.
$field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']);
$this->assertTrue(empty($field_storage), 'A deleted storage is not loaded by default.');
$this->assertTrue(empty($field_storage), 'Field storage was deleted');
// Try to load the field normally and make sure it does not show up.
$field = FieldConfig::load('entity_test.' . '.' . $field_definition['bundle'] . '.' . $field_definition['field_name']);
$this->assertTrue(empty($field), 'A field whose storage was deleted is not loaded by default.');
$this->assertTrue(empty($field), 'Field was deleted');
// Make sure the other field and its storage are not deleted.
$another_field_storage = FieldStorageConfig::load('entity_test.' . $another_field_storage_definition['field_name']);
......
......@@ -35,15 +35,6 @@ function media_install() {
}
}
/**
* Implements hook_uninstall().
*
* @TODO Remove when https://www.drupal.org/node/2884202 is fixed.
*/
function media_uninstall() {
\Drupal::moduleHandler()->invoke('field', 'cron');
}
/**
* Implements hook_requirements().
*/
......
<?php
namespace Drupal\Tests\system\Kernel\Installer;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the uninstallation of modules.
*
* @group Module
*/
class UninstallKernelTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'user', 'field', 'file', 'image', 'media'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('user', ['users_data']);
$this->installEntitySchema('media');
$this->installEntitySchema('file');
$this->installConfig(['media']);
}
/**
* Tests uninstalling media and file modules.
*/
public function testUninstallMedia() {
// Media creates a file field that is removed on uninstall, ensure that it
// is fully deleted (as it is empty) and that file then can be uninstalled
// as well.
\Drupal::service('module_installer')->uninstall(['media']);
\Drupal::service('module_installer')->uninstall(['file']);
}
}
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