I have also get this kind of issue that change field type from one to another with existing data. So as @Eyal mentioned, we can write an update hook to
- export data from a field table to a variable
- remove fields of all bundles
- add new fields of all bundles
- restore data to field table
Here is the code example
use \Drupal\field\Entity\FieldStorageConfig;
use \Drupal\field\Entity\FieldConfig;
/**
* Change node__field_maps from string_long to string type.
*/
function mymodule_update_8XXX() {
$database = \Drupal::database();
$table = 'node__field_maps';
$entity_type = 'node';
$field_name = 'field_maps';
$field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
if (is_null($field_storage)) {
return;
}
$rows = NULL;
if ($database->schema()->tableExists($table)) {
// The table data to restore after the update is completed.
$rows = $database->select($table, 'n')
->fields('n')
->execute()
->fetchAll();
}
$new_fields = array();
// Use existing field config for new field.
foreach ($field_storage->getBundles() as $bundle => $label) {
$field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
$new_field = $field->toArray();
$new_field['field_type'] = 'string';
$new_field['settings'] = array();
$new_fields[] = $new_field;
}
// Deleting field storage which will also delete bundles(fields).
$new_field_storage = $field_storage->toArray();
$new_field_storage['type'] = 'string';
$new_field_storage['settings'] = array(
'max_length' => 255,
'is_ascii' => FALSE,
'case_sensitive' => FALSE,
);
$field_storage->delete();
// Purge field data now to allow new field and field_storage with same name
// to be created. You may need to increase batch size.
field_purge_batch(10);
// Create new field storage.
$new_field_storage = FieldStorageConfig::create($new_field_storage);
$new_field_storage->save();
// Create new fields.
foreach ($new_fields as $new_field) {
$new_field = FieldConfig::create($new_field);
$new_field->save();
}
// Restore existing data in the same table.
if (!is_null($rows)) {
foreach ($rows as $row) {
$database->insert($table)
->fields((array) $row)
->execute();
}
}
}
NOTE: Always backup DB and test code before running this code
Other thoughts
In my case, we also have exported configures which means the field type and settings are also included in field_storage_config, field_config, entity_form_display, and entity_view_display config files. So we have to update config settings in those files as well. One easy way could be:
- clone your website
- delete the field and add a field with the same name again in field UI
- export configures to use them in the repo
- make sure the config uuid for the field and storage were not changed (change the uuid back if it was) to avoid the import potentially removing data.
- import configure after DB update and clear cache again
This is code is inspired by @drugan in the post https://www.drupal.org/node/2816859