$invocations) { $actual_invocations = $actual_hooks[$hook]; // Check that the number of invocations is correct. $this->assertEqual(count($actual_invocations), count($invocations), "$hook() was called the expected number of times."); // Check that the hook was called for each expected argument. foreach ($invocations as $argument) { $found = FALSE; foreach ($actual_invocations as $actual_arguments) { // The argument we are looking for is either an array of entities as // the second argument or a single entity object as the first. if ($argument instanceof EntityInterface && $actual_arguments[0]->id() == $argument->id()) { $found = TRUE; break; } // In case of an array, compare the array size and make sure it // contains the same elements. elseif (is_array($argument) && count($actual_arguments[1]) == count($argument) && count(array_diff_key($actual_arguments[1], $argument)) == 0) { $found = TRUE; break; } } $this->assertTrue($found, "$hook() was called on expected argument"); } } } protected function setUp() { parent::setUp(); $this->fieldStorages = array(); $this->entities = array(); $this->entitiesByBundles = array(); // Create two bundles. $this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2'); foreach ($this->bundles as $name => $desc) { entity_test_create_bundle($name, $desc); } // Create two field storages. $field_storage = entity_create('field_storage_config', array( 'field_name' => 'bf_1', 'entity_type' => $this->entityTypeId, 'type' => 'test_field', 'cardinality' => 1 )); $field_storage->save(); $this->fieldStorages[] = $field_storage; $field_storage = entity_create('field_storage_config', array( 'field_name' => 'bf_2', 'entity_type' => $this->entityTypeId, 'type' => 'test_field', 'cardinality' => 4 )); $field_storage->save(); $this->fieldStorages[] = $field_storage; // For each bundle, create each field, and 10 entities with values for the // fields. foreach ($this->bundles as $bundle) { foreach ($this->fieldStorages as $field_storage) { entity_create('field_config', array( 'field_storage' => $field_storage, 'bundle' => $bundle, ))->save(); } for ($i = 0; $i < 10; $i++) { $entity = entity_create($this->entityTypeId, array('type' => $bundle)); foreach ($this->fieldStorages as $field_storage) { $entity->{$field_storage->getName()}->setValue($this->_generateTestFieldValues($field_storage->getCardinality())); } $entity->save(); } } $this->entities = entity_load_multiple($this->entityTypeId); foreach ($this->entities as $entity) { // This test relies on the entities having stale field definitions // so that the deleted field can be accessed on them. Access the field // now, so that they are always loaded. $entity->bf_1->value; // Also keep track of the entities per bundle. $this->entitiesByBundles[$entity->bundle()][$entity->id()] = $entity; } } /** * Verify that deleting a field leaves the field data items in the database * and that the appropriate Field API functions can operate on the deleted * data and field definition. * * This tests how EntityFieldQuery interacts with field deletion and could be * moved to FieldCrudTestCase, but depends on this class's setUp(). */ function testDeleteField() { $bundle = reset($this->bundles); $field_storage = reset($this->fieldStorages); $field_name = $field_storage->getName(); $factory = \Drupal::service('entity.query'); // There are 10 entities of this bundle. $found = $factory->get('entity_test') ->condition('type', $bundle) ->execute(); $this->assertEqual(count($found), 10, 'Correct number of entities found before deleting'); // Delete the field. $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name); $field->delete(); // The field still exists, deleted. $fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE)); $this->assertEqual(count($fields), 1, 'There is one deleted field'); $field = $fields[$field->uuid()]; $this->assertEqual($field->getTargetBundle(), $bundle, 'The deleted field is for the correct bundle'); // Check that the actual stored content did not change during delete. $storage = \Drupal::entityManager()->getStorage($this->entityTypeId); /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $storage->getTableMapping(); $table = $table_mapping->getDedicatedDataTableName($field_storage); $column = $table_mapping->getFieldColumnName($field_storage, 'value'); $result = db_select($table, 't') ->fields('t') ->execute(); foreach ($result as $row) { $this->assertEqual($this->entities[$row->entity_id]->{$field_name}->value, $row->$column); } // There are 0 entities of this bundle with non-deleted data. $found = $factory->get('entity_test') ->condition('type', $bundle) ->condition("$field_name.deleted", 0) ->execute(); $this->assertFalse($found, 'No entities found after deleting'); // There are 10 entities of this bundle when deleted fields are allowed, and // their values are correct. $found = $factory->get('entity_test') ->condition('type', $bundle) ->condition("$field_name.deleted", 1) ->sort('id') ->execute(); $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting'); $this->assertFalse(array_diff($found, array_keys($this->entities))); } /** * Verify that field data items and fields are purged when a field storage is * deleted. */ function testPurgeField() { // Start recording hook invocations. field_test_memorize(); $bundle = reset($this->bundles); $field_storage = reset($this->fieldStorages); $field_name = $field_storage->getName(); // Delete the field. $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name); $field->delete(); // No field hooks were called. $mem = field_test_memorize(); $this->assertEqual(count($mem), 0, 'No field hooks were called'); $batch_size = 2; for ($count = 8; $count >= 0; $count -= $batch_size) { // Purge two entities. field_purge_batch($batch_size); // There are $count deleted entities left. $found = \Drupal::entityQuery('entity_test') ->condition('type', $bundle) ->condition($field_name . '.deleted', 1) ->execute(); $this->assertEqual(count($found), $count, 'Correct number of entities found after purging 2'); } // Check hooks invocations. // FieldItemInterface::delete() should have been called once for each entity in the // bundle. $actual_hooks = field_test_memorize(); $hooks = array(); $entities = $this->entitiesByBundles[$bundle]; foreach ($entities as $id => $entity) { $hooks['field_test_field_delete'][] = $entity; } $this->checkHooksInvocations($hooks, $actual_hooks); // The field still exists, deleted. $fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE)); $this->assertEqual(count($fields), 1, 'There is one deleted field'); // Purge the field. field_purge_batch($batch_size); // The field is gone. $fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE)); $this->assertEqual(count($fields), 0, 'The field is gone'); // The field storage still exists, not deleted, because it has a second // field. $storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE)); $this->assertTrue(isset($storages[$field_storage->uuid()]), 'The field storage exists and is not deleted'); } /** * Verify that field storages are preserved and purged correctly as multiple * fields are deleted and purged. */ function testPurgeFieldStorage() { // Start recording hook invocations. field_test_memorize(); $field_storage = reset($this->fieldStorages); $field_name = $field_storage->getName(); // Delete the first field. $bundle = reset($this->bundles); $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name); $field->delete(); // Assert that FieldItemInterface::delete() was not called yet. $mem = field_test_memorize(); $this->assertEqual(count($mem), 0, 'No field hooks were called.'); // Purge the data. field_purge_batch(10); // Check hooks invocations. // FieldItemInterface::delete() should have been called once for each entity in the // bundle. $actual_hooks = field_test_memorize(); $hooks = array(); $entities = $this->entitiesByBundles[$bundle]; foreach ($entities as $id => $entity) { $hooks['field_test_field_delete'][] = $entity; } $this->checkHooksInvocations($hooks, $actual_hooks); // The field still exists, deleted. $fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE)); $this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted'); // Purge again to purge the field. field_purge_batch(0); // The field is gone. $fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE)); $this->assertEqual(count($fields), 0, 'The field is purged.'); // The field storage still exists, not deleted. $storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE)); $this->assertTrue(isset($storages[$field_storage->uuid()]) && !$storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is not deleted'); // Delete the second field. $bundle = next($this->bundles); $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name); $field->delete(); // Assert that FieldItemInterface::delete() was not called yet. $mem = field_test_memorize(); $this->assertEqual(count($mem), 0, 'No field hooks were called.'); // Purge the data. field_purge_batch(10); // Check hooks invocations (same as above, for the 2nd bundle). $actual_hooks = field_test_memorize(); $hooks = array(); $entities = $this->entitiesByBundles[$bundle]; foreach ($entities as $id => $entity) { $hooks['field_test_field_delete'][] = $entity; } $this->checkHooksInvocations($hooks, $actual_hooks); // The field and the storage still exist, deleted. $fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE)); $this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted'); $storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE)); $this->assertTrue(isset($storages[$field_storage->uuid()]) && $storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is deleted'); // Purge again to purge the field and the storage. field_purge_batch(0); // The field and the storage are gone. $fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE)); $this->assertEqual(count($fields), 0, 'The field is purged.'); $storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $field_storage->uuid(), 'include_deleted' => TRUE)); $this->assertEqual(count($storages), 0, 'The field storage is purged.'); } }