5

I'm struggling with writing an update hook that will simply update the length of a text base field attached to an entity of mine.

Here's my update hook:

function my_module_update_8001() {
  // Collect all existing story run data.
  $database = \Drupal::database();

  // Gather all the existing story run data.
  $tables = [
    'content_tile_field_data',
    'content_tile_field_revision',
  ];
  $existing_data = [];
  foreach ($tables as $table) {
    $existing_data[$table] = $database->select($table)
      ->fields($table)
      ->execute()
      ->fetchAll(PDO::FETCH_ASSOC);
    $database->truncate($table)->execute();
  }

  // Update the "name" base field definition to a new max length
  $update_manager = \Drupal::entityDefinitionUpdateManager();
  $name_field = $update_manager->getFieldStorageDefinition('name', 'content_tile');
  $name_field->setSetting('max_length', 100);
  $update_manager->updateFieldStorageDefinition($name_field);

  // Restore the data.
  foreach ($tables as $table) {
    $insert_query = $database
      ->insert($table)
      ->fields(array_keys(end($existing_data[$table])));
    foreach ($existing_data[$table] as $row) {
      $insert_query->values(array_values($row));
    }
    $insert_query->execute();
  }
}

After running this, the name field is clearly dropped and re-added (I know because it now appears at the end of the list of columns in the table), but the length is still set to 50, the old value.

I've narrowed this down to BaseFieldDefinition::getSchema():

  public function getSchema() {
    if (!isset($this->schema)) {
      // Get the schema from the field item class.
      $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType());
      $class = $definition['class'];
      $schema = $class::schema($this);
      // Fill in default values.
      $schema += array(
        'columns' => array(),
        'unique keys' => array(),
        'indexes' => array(),
        'foreign keys' => array(),
      );

      // Merge custom indexes with those specified by the field type. Custom
      // indexes prevail.
      $schema['indexes'] = $this->indexes + $schema['indexes'];

      $this->schema = $schema;
    }

    return $this->schema;
  }

The schema definition is cached, so the update process gets the old schema data instead of regenerating it. There appears to be no way to clear this internal cache.

If I comment out the code related to the caching, it works as expected.

Is there another approach to this that I'm missing?

EDIT: The approach I took instead is to just load the current definition and pass that through to the update manager, like this:

  $update_manager = \Drupal::entityDefinitionUpdateManager();
  $definitions = \Drupal::entityManager()->getFieldStorageDefinitions('content_tile');
  $name_field_definition = $definitions['name'];
  $update_manager->updateFieldStorageDefinition($name_field_definition);

I guess this is not recommended normally, but it worked just fine.

4 Answers 4

2

I was having the exact same issue, trying to update the "max_length" of the name field of a custom entity. I did a hook_update like that :

function mymodule_update_8005() {
  $entity_definition_update_manager = \Drupal::service('entity.definition_update_manager');
  $field_definition = $entity_definition_update_manager->getFieldStorageDefinition('name', 'myentityid');
  $field_definition->setSetting('max_length', 255);
  $entity_definition_update_manager->updateFieldStorageDefinition($field_definition);
}

But then, even if the fieldDefinition had correctly changed, I was still having the old length limitation.

Finally, what I had forgotten to do was changing this max_length in my entity definition itself, here :

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  $fields['name'] = BaseFieldDefinition::create('string')
  ->setLabel(t('Title'))
  ->setSettings(array(
    'max_length' => 255,
    'text_processing' => 0,
  ));
  ...

After that, it worked as desired.

3
  • Sadly, this doesn't work for me. I updated my setting in both places, but DB field property is still the same. Dec 18, 2017 at 21:55
  • I've also tried drush updatedb --entity-updates -y but order of updates is wrong, so it fails. Dec 18, 2017 at 22:07
  • This did work for me, after running the drush command above Feb 22, 2018 at 16:57
2

Offical Drupal documentation in the section "Altering the length of a field with data" has a example function for achieving that https://www.drupal.org/docs/8/api/update-api/updating-database-schema-andor-data-in-drupal-8#s-altering-the-length-of-a-field-with-data

1

In latest version of Drupal seems to work only reinstall option, so basic flow is:

$entityUpdateManager = \Drupal::entityDefinitionUpdateManager();
  $field_definition = $entityUpdateManager->getFieldStorageDefinition($fieldName, $entityTypeId);
  $entityUpdateManager->uninstallFieldStorageDefinition($field_definition);
  $newStorageDefinition = BaseFieldDefinition::create('string')
    ->setLabel(t('Label'))
    ->setSetting('max_length', 191)
    ->setDescription(t('Description.'))
    ->setDisplayConfigurable('form', TRUE)
    ->setDisplayConfigurable('view', TRUE); // Same as in entity type class.
  $entityUpdateManager->installFieldStorageDefinition(
    $fieldName, $entityTypeId, $moduleName, $newStorageDefinition
  );
4
  • 1
    Do you know of any reference/docs about this? The prior answer from 2017 doesn't work, and with no 'drush entity-updates' this is getting too confusing to have no documentation.
    – Dylan
    Sep 25, 2019 at 0:32
  • Basic info is drupal.org/node/3034742 But I tried out some variants to find working. Sep 26, 2019 at 6:11
  • 1
    This worked for me, but it obviously removes all the data also, which isn't ideal.
    – Dylan
    Sep 27, 2019 at 19:59
  • Yes uninstall, removes, so temporary field (property) may be solution. Sep 28, 2019 at 20:03
0

It is it bit confusing and, unless I've missed it, not well documented. I think the answer is to study what the devel_entity_updates module does, not to reproduce it in entirety but to see what you need to do for a specific field.

There are several services involved in updating a field. The one that does the work is FieldStorageDefinitionListener. Note that it does not work for a field with data so you probably need to copy to a temporary field and back.

Final code needs to be something like this:

/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $efm */
$efm = \Drupal::service('entity_field.manager');
$storage_definitions = $efm->getFieldStorageDefinitions('test_entity');
$new_field_def = $storage_definitions['field1'];

/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $elisr */
$elisr = \Drupal::service('entity.last_installed_schema.repository');
$original_storage_definitions = $elisr->getLastInstalledFieldStorageDefinitions('test_entity');
$orig_field_def = $original_storage_definitions['field1'];

/** @var \Drupal\Core\Field\FieldStorageDefinitionListenerInterface $fsdl */
$fsdl = \Drupal::service('field_storage_definition.listener');

$fsdl->onFieldStorageDefinitionUpdate($new_field_def, $orig_field_def);

That will update the schema.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.