annotate core/modules/field/field.purge.inc @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 1fec387a4317
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Provides support for field data purge after mass deletion.
Chris@0 6 */
Chris@0 7
Chris@14 8 use Drupal\Core\Field\FieldDefinitionInterface;
Chris@0 9 use Drupal\Core\Field\FieldException;
Chris@14 10 use Drupal\Core\Field\FieldStorageDefinitionInterface;
Chris@0 11
Chris@0 12 /**
Chris@0 13 * @defgroup field_purge Field API bulk data deletion
Chris@0 14 * @{
Chris@0 15 * Cleans up after Field API bulk deletion operations.
Chris@0 16 *
Chris@0 17 * Field API provides functions for deleting data attached to individual
Chris@0 18 * entities as well as deleting entire fields or field storages in a single
Chris@0 19 * operation.
Chris@0 20 *
Chris@0 21 * When a single entity is deleted, the Entity storage performs the
Chris@0 22 * following operations:
Chris@0 23 * - Invoking the method \Drupal\Core\Field\FieldItemListInterface::delete() for
Chris@0 24 * each field on the entity. A file field type might use this method to delete
Chris@0 25 * uploaded files from the filesystem.
Chris@0 26 * - Removing the data from storage.
Chris@0 27 * - Invoking the global hook_entity_delete() for all modules that implement it.
Chris@0 28 * Each hook implementation receives the entity being deleted and can operate
Chris@0 29 * on whichever subset of the entity's bundle's fields it chooses to.
Chris@0 30 *
Chris@0 31 * Similar operations are performed on deletion of a single entity revision.
Chris@0 32 *
Chris@0 33 * When a bundle, field or field storage is deleted, it is not practical to
Chris@0 34 * perform those operations immediately on every affected entity in a single
Chris@0 35 * page request; there could be thousands or millions of them. Instead, the
Chris@0 36 * appropriate field data items, fields, and/or field storages are marked as
Chris@0 37 * deleted so that subsequent load or query operations will not return them.
Chris@0 38 * Later, a separate process cleans up, or "purges", the marked-as-deleted data
Chris@0 39 * by going through the three-step process described above and, finally,
Chris@0 40 * removing deleted field storage and field records.
Chris@0 41 *
Chris@0 42 * Purging field data is made somewhat tricky by the fact that, while
Chris@0 43 * $entity->delete() has a complete entity to pass to the various deletion
Chris@0 44 * steps, the Field API purge process only has the field data it has previously
Chris@0 45 * stored. It cannot reconstruct complete original entities to pass to the
Chris@0 46 * deletion operations. It is even possible that the original entity to which
Chris@0 47 * some Field API data was attached has been itself deleted before the field
Chris@0 48 * purge operation takes place.
Chris@0 49 *
Chris@0 50 * Field API resolves this problem by using stub entities during purge
Chris@0 51 * operations, containing only the information from the original entity that
Chris@0 52 * Field API knows about: entity type, ID, revision ID, and bundle. It also
Chris@0 53 * contains the field data for whichever field is currently being purged.
Chris@0 54 *
Chris@0 55 * See @link field Field API @endlink for information about the other parts of
Chris@0 56 * the Field API.
Chris@0 57 */
Chris@0 58
Chris@0 59 /**
Chris@0 60 * Purges a batch of deleted Field API data, field storages, or fields.
Chris@0 61 *
Chris@0 62 * This function will purge deleted field data in batches. The batch size
Chris@0 63 * is defined as an argument to the function, and once each batch is finished,
Chris@0 64 * it continues with the next batch until all have completed. If a deleted field
Chris@0 65 * with no remaining data records is found, the field itself will
Chris@0 66 * be purged. If a deleted field storage with no remaining fields is found, the
Chris@0 67 * field storage itself will be purged.
Chris@0 68 *
Chris@14 69 * @param int $batch_size
Chris@0 70 * The maximum number of field data records to purge before returning.
Chris@14 71 * @param string $field_storage_unique_id
Chris@14 72 * (optional) Limit the purge to a specific field storage. Defaults to NULL.
Chris@0 73 */
Chris@14 74 function field_purge_batch($batch_size, $field_storage_unique_id = NULL) {
Chris@14 75 /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
Chris@14 76 $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
Chris@14 77
Chris@14 78 $fields = $deleted_fields_repository->getFieldDefinitions($field_storage_unique_id);
Chris@0 79
Chris@0 80 $info = \Drupal::entityManager()->getDefinitions();
Chris@0 81 foreach ($fields as $field) {
Chris@0 82 $entity_type = $field->getTargetEntityTypeId();
Chris@0 83
Chris@0 84 // We cannot purge anything if the entity type is unknown (e.g. the
Chris@0 85 // providing module was uninstalled).
Chris@0 86 // @todo Revisit after https://www.drupal.org/node/2080823.
Chris@0 87 if (!isset($info[$entity_type])) {
Chris@14 88 \Drupal::logger('field')->warning("Cannot remove field @field_name because the entity type is unknown: %entity_type", ['@field_name' => $field->getName(), '%entity_type' => $entity_type]);
Chris@0 89 continue;
Chris@0 90 }
Chris@0 91
Chris@0 92 $count_purged = \Drupal::entityManager()->getStorage($entity_type)->purgeFieldData($field, $batch_size);
Chris@0 93 if ($count_purged < $batch_size || $count_purged == 0) {
Chris@0 94 // No field data remains for the field, so we can remove it.
Chris@0 95 field_purge_field($field);
Chris@0 96 }
Chris@0 97 $batch_size -= $count_purged;
Chris@0 98 // Only delete up to the maximum number of records.
Chris@0 99 if ($batch_size == 0) {
Chris@0 100 break;
Chris@0 101 }
Chris@0 102 }
Chris@0 103
Chris@0 104 // Retrieve all deleted field storages. Any that have no fields can be purged.
Chris@14 105 foreach ($deleted_fields_repository->getFieldStorageDefinitions() as $field_storage) {
Chris@14 106 if ($field_storage_unique_id && $field_storage->getUniqueStorageIdentifier() != $field_storage_unique_id) {
Chris@0 107 // If a specific UUID is provided, only purge the corresponding field.
Chris@0 108 continue;
Chris@0 109 }
Chris@0 110
Chris@0 111 // We cannot purge anything if the entity type is unknown (e.g. the
Chris@0 112 // providing module was uninstalled).
Chris@0 113 // @todo Revisit after https://www.drupal.org/node/2080823.
Chris@0 114 if (!isset($info[$field_storage->getTargetEntityTypeId()])) {
Chris@0 115 continue;
Chris@0 116 }
Chris@0 117
Chris@14 118 $fields = $deleted_fields_repository->getFieldDefinitions($field_storage->getUniqueStorageIdentifier());
Chris@0 119 if (empty($fields)) {
Chris@0 120 field_purge_field_storage($field_storage);
Chris@0 121 }
Chris@0 122 }
Chris@0 123 }
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Purges a field record from the database.
Chris@0 127 *
Chris@0 128 * This function assumes all data for the field has already been purged and
Chris@0 129 * should only be called by field_purge_batch().
Chris@0 130 *
Chris@14 131 * @param \Drupal\Core\Field\FieldDefinitionInterface $field
Chris@14 132 * The field to purge.
Chris@0 133 */
Chris@14 134 function field_purge_field(FieldDefinitionInterface $field) {
Chris@14 135 /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
Chris@14 136 $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
Chris@14 137 $deleted_fields_repository->removeFieldDefinition($field);
Chris@0 138
Chris@0 139 // Invoke external hooks after the cache is cleared for API consistency.
Chris@0 140 \Drupal::moduleHandler()->invokeAll('field_purge_field', [$field]);
Chris@0 141 }
Chris@0 142
Chris@0 143 /**
Chris@0 144 * Purges a field record from the database.
Chris@0 145 *
Chris@0 146 * This function assumes all fields for the field storage has already been
Chris@0 147 * purged, and should only be called by field_purge_batch().
Chris@0 148 *
Chris@14 149 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage
Chris@0 150 * The field storage to purge.
Chris@0 151 *
Chris@14 152 * @throws \Drupal\Core\Field\FieldException
Chris@0 153 */
Chris@14 154 function field_purge_field_storage(FieldStorageDefinitionInterface $field_storage) {
Chris@14 155 /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
Chris@14 156 $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
Chris@14 157
Chris@14 158 $fields = $deleted_fields_repository->getFieldDefinitions($field_storage->getUniqueStorageIdentifier());
Chris@0 159 if (count($fields) > 0) {
Chris@0 160 throw new FieldException(t('Attempt to purge a field storage @field_name that still has fields.', ['@field_name' => $field_storage->getName()]));
Chris@0 161 }
Chris@0 162
Chris@14 163 $deleted_fields_repository->removeFieldStorageDefinition($field_storage);
Chris@0 164
Chris@0 165 // Notify the storage layer.
Chris@0 166 \Drupal::entityManager()->getStorage($field_storage->getTargetEntityTypeId())->finalizePurge($field_storage);
Chris@0 167
Chris@0 168 // Invoke external hooks after the cache is cleared for API consistency.
Chris@0 169 \Drupal::moduleHandler()->invokeAll('field_purge_field_storage', [$field_storage]);
Chris@0 170 }
Chris@0 171
Chris@0 172 /**
Chris@0 173 * @} End of "defgroup field_purge".
Chris@0 174 */