Do you want to know how to Drupal?

Let's Drupal

How to change the type of the field in Drupal 9?

There are many cases when we need to switch a type of a field from one to another based on a new business requirement. How to do it and keep the old data.

First you HAVE TO make a back up :) 

I will provide the full code and then step by step we will break it down. To perform this operation we will use hook_update.

Steps to change the type of the field

  1. Load field storage
  2. Fetch the data for this field
  3. Configure a new field storage
  4. Delete the old storage
  5. Purge the data
  6. Create a new storage configuration
  7. Add field to bundles
  8. Insert the data back to db
<?php
use \Drupal\field\Entity\FieldStorageConfig;
use \Drupal\field\Entity\FieldConfig;

/**
 * Change node__field_quote from string_long to string type.
 */
function yourmodule_update_8XXX() {
  $database = \Drupal::database();
  $table = 'node__field_quote ';
  $entity_type = 'node';
  $field_name = 'field_quote';

  // #1 Load storage configuration.
  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

  // if storage config is not found, there is nothing to do.
  if (is_null($field_storage)) {
    return;
  }

  $rows = [];

  // #2 Fetch the data for the old field.
  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 = [];

  // 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'] = [];

    $new_fields[] = $new_field;
  }

  // #3 Take the old storage configuration present it as an array and update it's settings.
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = [
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  ];

  // #4 Deleting field storage which will also delete bundles(fields).
  $field_storage->delete();

  // #5 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(100);

  // #6 Create new field storage, create a new storage based on configuration above.
  $new_field_storage = FieldStorageConfig::create($new_field_storage);
  $new_field_storage->save();

  // #7 Create new fields.
  foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
  }

  // #8 Restore existing data in the same table.
  if (!empty($rows)) {
    foreach ($rows as $row) {
	  // We can do some manipulation with data here.
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }

}

 

Step 1 - Load the old field storage

First of all of course we need to load the old field storage configuration. A field storage always attached to entity type: node, media, paragraph and etc.

  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

Step 2 - Fetch the data for this field

We fetch all the data for the old field, for any fields and for any bundles. For example this fiels used in articles (node content type) and pages (node content type). We want to keep the data for all of the fields. Later we can perform some transformation of the data. Keep in mind if you have many items in this table, you will need to use batch operations.

 

// #2 Fetch the data for the old field.
  if ($database->schema()->tableExists($table)) {
    // The table data to restore after the update is completed.
    $rows = $database->select($table, 'n')
      ->fields('n')
      ->execute()
      ->fetchAll();
  }

 

Step 3  -  Configure a new field storage

In this example we actually use the old storage configuration and just update few settings.

// #3 Take the old storage configuration present it as an array and update it's settings.
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = [
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  ];

But in some cases we can get it from configuration files or just pass an array with configuration directly. For example
 

// Get field storage from config file.
$install_storage = new ExtensionInstallStorage(
    \Drupal::service('config.storage'),
    InstallStorage::CONFIG_INSTALL_DIRECTORY
);
$field_storage_config = $install_storage->read("field.storage.node.field_quote");

Step 4  -  Delete the old storage

// #4 Deleting field storage which will also delete bundles(fields).
  $field_storage->delete();

 

Step 5 - Purge the data

Before recreating the new field we need to delete all the data for these fields. If you have a lot of items consider to change the value.

// #5 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(100);

 

Step 6 - Create a new storage configuration

Here we just pass a field storage configuration to create method as an array

// #6 Create new field storage, create a new storage based on configuration above.
$new_field_storage = FieldStorageConfig::create($new_field_storage);
$new_field_storage->save();

 

Step 7 - Add field to bundles

We recreate field instances here. We again use the previous configuration of the field and update the settings.
 

// #7 Create new fields.
foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
}

and then we can also recreate the field completely from scratch, something like this

FieldConfig::create([
      'field_storage' => FieldStorageConfig::loadByName($entity_type, $field_name),
      'bundle' => $bundle,
      'label' => t('Our field label'),
      'required' => TRUE,
      'default_value' => [[
        'value' => 1,
      ],
      ],
])->save();

 

Step 8 - Insert the data back to the field table.

Here we can convert data or just insert as it is.

  // #8 Restore existing data in the same table.
  if (!empty($rows)) {
    foreach ($rows as $row) {
      // We can do some manipulation with data here.
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }