Drupal Answers Asked by Coder1 on December 8, 2021
I have created a D8 custom field that extends FieldBaseItem
. It is already in use storing data.
How do I get the schema updated in the database, for an existing field?
What I’ve done:
I have added a new column by adding to propertyDefinitions
and schema()
accordingly.
Here’s an example schema. Let’s say I just added the admin
column.
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = [
'columns' => [
'state' => [
'type' => 'varchar',
'length' => 2
],
'admin' => [
'type' => 'int',
'size' => 'tiny'
],
...
I’ve found posts on how to do this in hook_update_N
in D7, but it’s unclear how to do this in D8.
Since entup
is not longer available in Drupal 8, many people have struggled with this, and many modules contain copious amounts of code in their update hooks to deal with this. If you know exactly which field instances need to be updated, you can update their tables directly, but this is not an option if you have a module that provides a FieldType
, and you are writing an update hook for that module. You want all instances of your field type to be detected and updated.
This Drupal core issue has some code examples for adding and removing properties from existing field types while updating all instances of that field type: https://www.drupal.org/project/drupal/issues/937442#comment-13760432 . Credit goes to https://www.drupal.org/u/robbinzhao . I just cleaned up the code and made it into a utility class:
<?php
namespace Drupalmy_utilities;
use Drupal;
use DrupalCoreEntitySqlSqlContentEntityStorage;
/**
* Utilities for updating field type definitions.
*
* Based on https://www.drupal.org/project/drupal/issues/937442#comment-12586376
*/
class FieldTypeUpdateUtil {
/**
* Add a new column for fieldType.
*
* @param string $field_type
* The ID of the field type definition.
* @param string $property
* The name of the property whose column to add.
*
* @throws DrupalComponentPluginExceptionInvalidPluginDefinitionException
* @throws DrupalComponentPluginExceptionPluginNotFoundException
* @throws DrupalCoreDatabaseSchemaObjectDoesNotExistException
* @throws DrupalCoreDatabaseSchemaObjectExistsException
*/
public static function addProperty($field_type, $property) {
$manager = Drupal::entityDefinitionUpdateManager();
$field_map = Drupal::service('entity_field.manager')
->getFieldMapByFieldType($field_type);
foreach ($field_map as $entity_type_id => $fields) {
foreach (array_keys($fields) as $field_name) {
$field_storage_definition = $manager->getFieldStorageDefinition($field_name, $entity_type_id);
$storage = Drupal::entityTypeManager()->getStorage($entity_type_id);
if ($storage instanceof SqlContentEntityStorage) {
$table_mapping = $storage->getTableMapping([
$field_name => $field_storage_definition,
]);
$table_names = $table_mapping->getDedicatedTableNames();
$columns = $table_mapping->getColumnNames($field_name);
foreach ($table_names as $table_name) {
$field_schema = $field_storage_definition->getSchema();
$schema = Drupal::database()->schema();
$field_exists = $schema->fieldExists($table_name, $columns[$property]);
$table_exists = $schema->tableExists($table_name);
if (!$field_exists && $table_exists) {
$schema->addField($table_name, $columns[$property], $field_schema['columns'][$property]);
}
}
}
$manager->updateFieldStorageDefinition($field_storage_definition);
}
}
}
/**
* Remove a property and column from field_type.
*
* @param string $field_type
* The ID of the field type definition.
* @param string $property
* The name of the property whose column to remove.
*
* @throws DrupalComponentPluginExceptionInvalidPluginDefinitionException
* @throws DrupalComponentPluginExceptionPluginNotFoundException
* @throws DrupalCoreEntitySqlSqlContentEntityStorageException
*/
public static function removeProperty($field_type, $property) {
$field_map = Drupal::service('entity_field.manager')
->getFieldMapByFieldType($field_type);
foreach ($field_map as $entity_type_id => $fields) {
foreach (array_keys($fields) as $field_name) {
self::removePropertyFromEntityType($entity_type_id, $field_name, $property);
}
}
}
/**
* Inner function, called by removeProperty.
*
* @param string $entity_type_id
* The ID of the entity type.
* @param string $field_name
* The ID of the field type definition.
* @param string $property
* The name of the property whose column to remove.
*
* @throws DrupalComponentPluginExceptionPluginNotFoundException
* @throws DrupalComponentPluginExceptionInvalidPluginDefinitionException
* @throws DrupalCoreEntitySqlSqlContentEntityStorageException
*/
private static function removePropertyFromEntityType($entity_type_id, $field_name, $property) {
$entity_type_manager = Drupal::entityTypeManager();
$entity_update_manager = Drupal::entityDefinitionUpdateManager();
$entity_storage_schema_sql = Drupal::keyValue('entity.storage_schema.sql');
$entity_type = $entity_type_manager->getDefinition($entity_type_id);
$field_storage_definition = $entity_update_manager->getFieldStorageDefinition($field_name, $entity_type_id);
$entity_storage = Drupal::entityTypeManager()->getStorage($entity_type_id);
/** @var DrupalCoreEntitySqlDefaultTableMapping $table_mapping */
$table_mapping = $entity_storage->getTableMapping([
$field_name => $field_storage_definition,
]);
// Load the installed field schema so that it can be updated.
$schema_key = "$entity_type_id.field_schema_data.$field_name";
$field_schema_data = $entity_storage_schema_sql->get($schema_key);
// Get table name and revision table name, getFieldTableName NOT WORK,
// so use getDedicatedDataTableName.
$table = $table_mapping->getDedicatedDataTableName($field_storage_definition);
// try/catch.
$revision_table = NULL;
if ($entity_type->isRevisionable() && $field_storage_definition->isRevisionable()) {
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
$revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
}
elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
$revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
}
}
// Save changes to the installed field schema.
if ($field_schema_data) {
$_column = $table_mapping->getFieldColumnName($field_storage_definition, $property);
// Update schema definition in database.
unset($field_schema_data[$table]['fields'][$_column]);
if ($revision_table) {
unset($field_schema_data[$revision_table]['fields'][$_column]);
}
$entity_storage_schema_sql->set($schema_key, $field_schema_data);
// Try to drop field data.
Drupal::database()->schema()->dropField($table, $_column);
}
}
}
Answered by rudolfbyker on December 8, 2021
In fact, like it's a custom field type, your change in the schema can be detected and applied if you launch the drush command drush entity-updates
(or drush entup
).
All tables which implement this field (like node__field_my_field_type
or paragraph__field_my_field_type
) will be updated. So you don't need to use an hook_update to update the schema of all these tables.
The big problem with this command is many errors are silent and sometimes, the command don't return errors but the schema hasn't been updated and still proposed by the command to be updated.
Answered by Claire D on December 8, 2021
The update hooks are essentially the same as Drupal 7, but it's recommended to use the DB Connection object with the addField() method.
The update hook to add a column might look like this:
$spec = array(
'type' => 'varchar',
'description' => "New Col",
'length' => 20,
'not null' => FALSE,
);
$schema = Database::getConnection()->schema();
$schema->addField('mytable1', 'newcol', $spec);
Full documentation can be found here: https://www.drupal.org/docs/8/api/update-api/updating-database-schema-andor-data-in-drupal-8
Answered by joegl on December 8, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP