Drupal — Field Type Migration
Tags: php, drupal, database
Published: 2020-09-28 02:24:01

---
title: Drupal — Field Type Migration
published: true
description: 
tags: PHP, Drupal, Database
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/pswb5redo7vj82t2cher.png
---
There are multiple ways of migrating data between different field types and this brief walkthrough will shed a light on one possible way of doing so.

> Field is [a Drupal core module](https://api.drupal.org/api/drupal/core!modules!field!field.module/group/field/9.0.x) and it’s part of [a list of different ways to represent data in Drupal](https://api.drupal.org/api/drupal/9.0.x#store_retrieve).

The process will include:
1. Creating a custom module.
2. Preparing migration variables.
3. Storing existing data.
4. Getting field config storage.
5. Getting field config.
6. Remove old configurations.
7. Insert data into the new fields configs.
8. Run the migration (aka. database updates).

#### **Prerequisites**

* Fresh or existing Drupal 9.0 project.
* Drush CLI tool (`composer require drush/drush`)

### **Things to keep in mind**

* This walkthrough will try to convert a field from Number (Integer) to Text (Formatted). [See documentation for list of field types](https://www.drupal.org/docs/8/api/entity-api/fieldtypes-fieldwidgets-and-fieldformatters#s-core-field-types).

**1- Creating a custom module**
There are [abundant list of documentations](https://www.drupal.org/docs/creating-custom-modules) when it comes to creating custom modules in Drupal. However, the minimum folder structure for our purpose is (and I am calling this custom module `field_migration_medium`):
```
field_migration_medium/
  field_migration_medium.info.yml
  field_migration_medium.install
```
In the `field_migration_medium.install` file, we will use `hook_update_N` [function](https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Extension!module.api.php/function/hook_update_N/9.0.x) to let Drupal know of an existing database update for this custom module.
```php
<?php
/**
* hook_update_N()
*/
function field_migration_medium_update_9001() {
}
```
> **N** donates the update ID and Drupal keeps track of this for each module. [See the documentation for more info](https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Extension!module.api.php/function/hook_update_N/9.0.x).

**2- Preparing migration variables**
We need to identify the current field type and the new field type. In order to identify these values, we need to look at what does [Field Config Storage](https://api.drupal.org/api/drupal/core!modules!field!src!FieldConfigStorage.php/class/FieldConfigStorage/9.0.x) and [Field Config](https://api.drupal.org/api/drupal/core!modules!field!src!Entity!FieldConfig.php/class/FieldConfig/9.0.x) mean.

A [bundle](https://www.drupal.org/docs/drupal-apis/entity-api/bundles) in Drupal consists of various field types such as Number, Text, Boolean … etc. Each of these field types has a default configuration setting ([Field Storage Configuration](https://api.drupal.org/api/drupal/core!modules!field!src!FieldConfigStorage.php/class/FieldConfigStorage/9.0.x)) and a specific one ([Field Configuration](https://api.drupal.org/api/drupal/core!modules!field!src!Entity!FieldConfig.php/class/FieldConfig/9.0.x)).

For example, I have created `field_number` for three different content types `article`, `page` and `medium`. All of these content types share the same `field_number` global configuration, however, each content type has it’s own implementation and default settings of `field_number`.

To illustrate these differences, below are the snippets of each of these objects (Field Storage Configuration and Field Configuration):

* **Global Configuration**: Field Storage Config object for `field_number`.
```
Drupal\field\Entity\FieldStorageConfig Object                                
  (                                                                            
      [id:protected] => node.field_number                                      
      [field_name:protected] => field_number                                   
      [entity_type:protected] => node                                          
      [type:protected] => integer                                              
      [module:protected] => core
```
* **Specific Configuration**: Field Config object for `article` content type.
```
Drupal\field\Entity\FieldConfig Object                                
  (                                                                            
      [deleted:protected] =>                                                   
      [fieldStorage:protected] =>                                              
      [id:protected] => node.article.field_number                              
      [field_name:protected] => field_number                                   
      [field_type:protected] => integer                                        
      [entity_type:protected] => node                                          
      [bundle:protected] => article                                            
      [label:protected] => Number                                              
      [description:protected] =>                                               
      [settings:protected] => Array                                            
          (                                                                    
              [min] =>                                                         
              [max] =>                                                         
              [prefix] =>                                                      
              [suffix] =>                                                      
          )
```
* **Specific Configuration**: Field Config object for `page` content type.
```
pageDrupal\field\Entity\FieldConfig Object                                   
  (                                                                            
      [deleted:protected] =>                                                   
      [fieldStorage:protected] =>                                              
      [id:protected] => node.page.field_number                                 
      [field_name:protected] => field_number                                   
      [field_type:protected] => integer                                        
      [entity_type:protected] => node                                          
      [bundle:protected] => page                                               
      [label:protected] => Number                                              
      [description:protected] =>                                               
      [settings:protected] => Array                                            
          (                                                                    
              [min] => 0                                                       
              [max] => 11                                                      
              [prefix] =>                                                      
              [suffix] =>                                                      
          )
```
* **Specific Configuration**: Field Config object for `medium` content type.
```
mediumDrupal\field\Entity\FieldConfig Object                                 
  (                                                                            
      [deleted:protected] =>                                                   
      [fieldStorage:protected] =>                                              
      [id:protected] => node.medium.field_number                               
      [field_name:protected] => field_number                                   
      [field_type:protected] => integer                                        
      [entity_type:protected] => node                                          
      [bundle:protected] => medium                                             
      [label:protected] => Number Text                                         
      [description:protected] => This is medium number text.                   
      [settings:protected] => Array                                            
          (                                                                    
              [min] =>                                                         
              [max] =>                                                         
              [prefix] =>                                                      
              [suffix] =>                                                      
          )
```
> There are subtle changes in each content type, such as `id`, `bundle`, `label`, `description` and `settings`. The common values in these content types are `field_name`, `field_type` and `entity_type`.

Keeping that in mind, let’s continue on and define the migration variables.

The migration variables are:
* Entity type — `node` in this walkthrough.
* Machine name of the old_field. The machine name can be found in the `/admin/reports/fields`. I will call this field `field_number`.

![Field List Table](https://dev-to-uploads.s3.amazonaws.com/i/o1vhhhles1a4oyxix51o.png)

* Table name of the old_field in the database. The table name will consist of the entity name and the old_field machine name `node__field_number`
* Revision table of the old_field; if exists. That will also have the entity name and the machine name of the old_field. `node_revision__field_number`
* An array to store the new fields config
```php
$entityType = 'node'; 
$oldFieldName = 'field_number';
$table = $entityType. '__' . $oldFieldName;
$revisionTable = $entityType. '_revision__' . $oldFieldName;
$newFieldsArray = [];
```
**3- Store data and revision data into arrays**
```php
$rows = NULL;
$revisionRows = NULL;
if ($database->schema()->tableExists($table)) {
    $rows = $database->select($table, 'n')->fields('n')->execute()->fetchAll();
    $revisionRows = $database->select($revisionTable, 'n')->fields('n')->execute()->fetchAll();
}
```
**4- Get Field Storage Configuration**
Since there is a single field storage config, we need to load the configuration by passing `$entityType` and `$oldFieldName` to `loadByName($entity_type_id, $field_name)` [method](https://api.drupal.org/api/drupal/core!modules!field!src!Entity!FieldStorageConfig.php/function/FieldStorageConfig%3A%3AloadByName/9.0.x).
```php
$fieldStorage = FieldStorageConfig::loadByName($entityType, $oldFieldName);
```
* Define the new field storage configuration
```php
$newFieldStorage = $fieldStorage->toArray();
$newFieldStorage['type'] = 'text';
$newFieldStorage['settings'] = array(
   'max_length' => 255
)
```
> **Note**: To get a list of the bundles that uses the field storage config, `getBundles()` [method](https://api.drupal.org/api/drupal/core%21modules%21field%21src%21Entity%21FieldStorageConfig.php/function/FieldStorageConfig%3A%3AgetBundles/9.0.x) can be called like this `$fieldStorage->getBundles()` as we will see below.

**5- Get Fields Configuration**
* Get all the bundles that has `field_number` and iterate through them.
* [Load the name](https://api.drupal.org/api/drupal/core%21modules%21field%21src%21Entity%21FieldConfig.php/function/FieldConfig%3A%3AloadByName/9.0.x) of each of these specific field config `loadByName($entity_type_id, $bundle, $field_name)`
```php
foreach ($fieldStorage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entityType, $bundle, $oldFieldName);
    // Turn the result into an array
    $newField = $field->toArray();
    // Update the field_type from `number` to `text`
    $newField['field_type'] = 'text';
    // Update the settings of this new field -- Optional
    $newField['settings'] = array(
           'max_length' => 255
    );
    // Store the result into an array
    $newFieldsArray[] = $newField;
}
```
**6- Remove old configurations**
* Delete old field storage configuration
```php
$fieldStorage->delete();
```
* Purge all fields value in batches
```php
field_purge_batch(N); // N is the batch number
```
**7- Insert data into the new fields configs**
* Create the new field storage configuration
```php
$newFieldStorage = FieldStorageConfig::create($newFieldStorage);
$newFieldStorage->save();
```
* Create the new fields configurations
```php
foreach ($newFieldsArray as $field) {
   $fieldConfig = FieldConfig::create($field);
   $fieldConfig->save();
}
```
* Insert data back into the new fields
```php
// Row data
if (!is_null($rows)) {
  foreach ($rows as $row) {
   $database->insert($table)->fields((array) $row)->execute();
  }
}
// Revision data
if (!is_null($revision_rows)) {
  foreach ($revision_rows as $row) {
    $database->insert($revisionTable)->fields((array) $row)->execute();
  }
}
```
**8- Run database update**
This update can be done either by using Drush or Drupal CLI commands. **Note**: Backup your database.
```
// Drupal CLI
bin/drupal upex
// Drush CLI
bin/drush updb
```

**The Full Code Snippet**
```php
<?php

use \Drupal\field\Entity\FieldConfig;
use \Drupal\field\Entity\FieldStorageConfig;

/**
 * Change field_number from Number (integer) to Text (formatted)
 */
function field_migration_medium_update_9001() {

  $entityType = 'node';
  $oldFieldName = 'field_number';
  $table = $entityType . '__' . $oldFieldName;
  $revisionTable = $entityType . '_revision__' . $oldFieldName;
  $newFieldsArray = [];

  $database = \Drupal::database();

  $rows = NULL;
  $revisionRows = NULL;
  if ($database->schema()->tableExists($table)) {
    $rows = $database->select($table, 'n')->fields('n')->execute()
      ->fetchAll();
    $revisionRows = $database->select($revisionTable, 'n')->fields('n')->execute()
      ->fetchAll();
  }
  
  // Get field storage config.
  $fieldStorage = FieldStorageConfig::loadByName($entityType, $oldFieldName);

  // Check if field storage config exist.
  if (is_null($fieldStorage)) {
    return t('Field Storage does not exist');
  }

  $newFieldStorage = $fieldStorage->toArray();
  $newFieldStorage['type'] = 'text';
  $newFieldStorage['settings'] = array(
    'max_length' => 255
  );
  
  foreach ($fieldStorage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entityType, $bundle, $oldFieldName);
    
    $newField = $field->toArray();
    $newField['field_type'] = 'text';
    $newField['settings'] = array(
      'max_length' => 255
    );
    $newFieldsArray[] = $newField;
  }
  
  $fieldStorage->delete();
  field_purge_batch(40);
  
  // Create new field storage.
  $newFieldStorage = FieldStorageConfig::create($newFieldStorage);
  $newFieldStorage->save();
  
  // Create new fields.
  foreach ($newFieldsArray as $field) {
    $fieldConfig = FieldConfig::create($field);
    $fieldConfig->save();
  }

  // Restore existing data in new fields.
  if (!is_null($rows)) {
    foreach ($rows as $row) {
    $database->insert($table)
             ->fields((array) $row)
             ->execute();
    }
  }

  if (!is_null($revisionRows)) {
    foreach ($revisionRows as $row) {
      $database->insert($revisionTable)
               ->fields((array) $row)
               ->execute();
     }
  }
}
```
**Final Thoughts**
When the database update runs, Drupal will keep track of the Update ID for each module. This [Update ID can be set to specific version](https://api.drupal.org/api/drupal/core%21includes%21schema.inc/function/drupal_set_installed_schema_version/9.0.x) by using Drush. **Use it carefully**:
```
drush ev "drupal_set_installed_schema_version(module, version)"
```
Happy coding :)