3 namespace Drupal\field;
5 use Drupal\Core\Config\ConfigImporter;
6 use Drupal\Core\Config\Entity\ConfigEntityStorage;
7 use Drupal\field\Entity\FieldStorageConfig;
10 * Processes field purges before a configuration synchronization.
12 class ConfigImporterFieldPurger {
15 * Processes fields targeted for purge as part of a configuration sync.
17 * This takes care of deleting the field if necessary, and purging the data on
20 * @param array $context
22 * @param \Drupal\Core\Config\ConfigImporter $config_importer
23 * The config importer.
25 public static function process(array &$context, ConfigImporter $config_importer) {
26 if (!isset($context['sandbox']['field'])) {
27 static::initializeSandbox($context, $config_importer);
30 // Get the list of field storages to purge.
31 $field_storages = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
32 // Get the first field storage to process.
33 $field_storage = reset($field_storages);
34 if (!isset($context['sandbox']['field']['current_storage_id']) || $context['sandbox']['field']['current_storage_id'] != $field_storage->id()) {
35 $context['sandbox']['field']['current_storage_id'] = $field_storage->id();
36 // If the storage has not been deleted yet we need to do that. This is the
37 // case when the storage deletion is staged.
38 if (!$field_storage->isDeleted()) {
39 $field_storage->delete();
42 field_purge_batch($context['sandbox']['field']['purge_batch_size'], $field_storage->getUniqueStorageIdentifier());
43 $context['sandbox']['field']['current_progress']++;
44 $fields_to_delete_count = count(static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete')));
45 if ($fields_to_delete_count == 0) {
46 $context['finished'] = 1;
49 $context['finished'] = $context['sandbox']['field']['current_progress'] / $context['sandbox']['field']['steps_to_delete'];
50 $context['message'] = \Drupal::translation()->translate('Purging field @field_label', ['@field_label' => $field_storage->label()]);
55 * Initializes the batch context sandbox for processing field deletions.
57 * This calculates the number of steps necessary to purge all the field data
58 * and saves data for later use.
60 * @param array $context
62 * @param \Drupal\Core\Config\ConfigImporter $config_importer
63 * The config importer.
65 protected static function initializeSandbox(array &$context, ConfigImporter $config_importer) {
66 $context['sandbox']['field']['purge_batch_size'] = \Drupal::config('field.settings')->get('purge_batch_size');
67 // Save the future list of installed extensions to limit the amount of times
68 // the configuration is read from disk.
69 $context['sandbox']['field']['extensions'] = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
71 $context['sandbox']['field']['steps_to_delete'] = 0;
72 $fields = static::getFieldStoragesToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
73 foreach ($fields as $field) {
74 $row_count = \Drupal::entityManager()->getStorage($field->getTargetEntityTypeId())
75 ->countFieldData($field);
77 // The number of steps to delete each field is determined by the
78 // purge_batch_size setting. For example if the field has 9 rows and the
79 // batch size is 10 then this will add 1 step to $number_of_steps.
80 $how_many_steps = ceil($row_count / $context['sandbox']['field']['purge_batch_size']);
81 $context['sandbox']['field']['steps_to_delete'] += $how_many_steps;
84 // Each field possibly needs one last field_purge_batch() call to remove the
85 // last field and the field storage itself.
86 $context['sandbox']['field']['steps_to_delete'] += count($fields);
88 $context['sandbox']['field']['current_progress'] = 0;
92 * Gets the list of fields to purge before configuration synchronization.
94 * If, during a configuration synchronization, a field is being deleted and
95 * the module that provides the field type is being uninstalled then the field
96 * data must be purged before the module is uninstalled. Also, if deleted
97 * fields exist whose field types are provided by modules that are being
98 * uninstalled their data need to be purged too.
100 * @param array $extensions
101 * The list of extensions that will be enabled after the configuration
102 * synchronization has finished.
103 * @param array $deletes
104 * The configuration that will be deleted by the configuration
107 * @return \Drupal\field\Entity\FieldStorageConfig[]
108 * An array of field storages that need purging before configuration can be
111 public static function getFieldStoragesToPurge(array $extensions, array $deletes) {
112 $providers = array_keys($extensions['module']);
113 $providers[] = 'core';
114 $storages_to_delete = [];
116 // Gather fields that will be deleted during configuration synchronization
117 // where the module that provides the field type is also being uninstalled.
118 $field_storage_ids = [];
119 foreach ($deletes as $config_name) {
120 $field_storage_config_prefix = \Drupal::entityManager()->getDefinition('field_storage_config')->getConfigPrefix();
121 if (strpos($config_name, $field_storage_config_prefix . '.') === 0) {
122 $field_storage_ids[] = ConfigEntityStorage::getIDFromConfigName($config_name, $field_storage_config_prefix);
125 if (!empty($field_storage_ids)) {
126 $field_storages = \Drupal::entityQuery('field_storage_config')
127 ->condition('id', $field_storage_ids, 'IN')
128 ->condition('module', $providers, 'NOT IN')
130 if (!empty($field_storages)) {
131 $storages_to_delete = FieldStorageConfig::loadMultiple($field_storages);
135 // Gather deleted fields from modules that are being uninstalled.
136 /** @var \Drupal\field\FieldStorageConfigInterface[] $deleted_storage_definitions */
137 $deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
138 foreach ($deleted_storage_definitions as $field_storage_definition) {
139 if ($field_storage_definition instanceof FieldStorageConfigInterface && !in_array($field_storage_definition->getTypeProvider(), $providers)) {
140 $storages_to_delete[$field_storage_definition->id()] = $field_storage_definition;
143 return $storages_to_delete;