Writing a BaseFieldDefinition hook_update_N

Before I wrote this code the "documentation" on this was in one of the two treasure troves (the other being the actual Drupal code itself) of the change records, specifically Write update functions for entity schema updates, automation removed.

As often happens the change record didn't quite cover what I wanted to do and it specifically didn't mention a couple of mind bending gotchas.

Trust the process

If you need to update the type of a custom BaseFieldDefinition the general process is:

  1. Store the existing values for the field
  2. Clear out the values from the field (required to uninstall a field)
  3. Uninstall the field
  4. Create a new BaseFieldDefinition
  5. Install the new definition
  6. Restore the values from step 1

For example, changing the type from boolean to string for which the code is:

$entity_type_manager = \Drupal::entityTypeManager();
$bundle_of = 'node';

$storage = $entity_type_manager->getStorage($bundle_of);
$bundle_definition = $entity_type_manager->getDefinition($bundle_of);
// Sometimes the primary key isn't 'id'. e.g. 'eid' or 'item_id'.
$id_key = $bundle_definition->getKey('id');
// If there is no data table defined then use the base table.
$table_name = $storage->getDataTable() ?? $storage->getBaseTable();
$database = \Drupal::database();
$definition_manager = \Drupal::entityDefinitionUpdateManager();

// Store the existing values.
$status_values = $database->select($table_name)
  ->fields($table_name, [$id_key, 'status_field'])
  ->execute()
  ->fetchAllKeyed();

// Clear out the values.
$database->update($table_name)
  ->fields(['status_field' => NULL])
  ->execute();

// Uninstall the field.
$field_storage_definition = $definition_manager->getFieldStorageDefinition('status_field', $bundle_of);
$definition_manager->uninstallFieldStorageDefinition($field_storage_definition);

// Create a new field definition.
$new_status_field = BaseFieldDefinition::create('string')
  ->setLabel(t('Status field'))
  ->setDescription(t('The status - either no, yes or skip.'))
  ->setDefaultValue('no')
  ->setRevisionable(FALSE)
  ->setTranslatable(FALSE);

// Install the new definition.
$definition_manager->installFieldStorageDefinition('status_field', $bundle_of, $bundle_of, $new_status_field);

// Restore the values.
$value_map = [
  '1' => 'yes',
  '0' => 'no',
];
foreach ($status_values as $id => $value) {
  $database->update($table_name)
    ->fields(['status_field' => $value_map[$value]])
    ->condition($id_key, $id)
    ->execute();
  }
}

Phew! I truly pity you if you have to do this for multiple defs and types.

Gotchas

So how about dem gotchas:

  • The primary key id for an entity probably differs in name
  • The name of the table that stores your BaseFieldDefinition probably differs in name pattern
  • You can't uninstall a definition until the field is cleared out

Bon chance, Drupalistas!

// @Category
// @Tags
// @Size
// @
18/08/2018