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:
- Store the existing values for the field
- Clear out the values from the field (required to uninstall a field)
- Uninstall the field
- Create a new
BaseFieldDefinition
- Install the new definition
- 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 = $ntity_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!
- Aug 19, 2018 ( Mar 21, 2022)