TransWikia.com

Add column to custom field schema

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.

3 Answers

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

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP