3

I want to add uuid property to existing custom entity extending ContentEntityBase. I've tried to a add the uuid entity key and installed field storage definition by:

function my_module_post_update_adduuid() {
  \Drupal::entityTypeManager()->clearCachedDefinitions();
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();

  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $field_storage_definition = BaseFieldDefinition::create('uuid')
    ->setLabel(new TranslatableMarkup('UUID'))
    ->setReadOnly(TRUE);

  $definition_update_manager->installFieldStorageDefinition('uuid', 'custom_entity_id', 'custom_entity_id', $field_storage_definition);
}

The "problem" is that for custom entity with data_table defined the property is added to the data_table and not to the base_table as expected. If the custom entity doesn't have data table defined the uuid property is written properly in base_table.

Everything seems to work even though the uuid property is defined in data_table but the schema is inconsistent compared to clean install with the uuid key present from the beginning.

1 Answer 1

3

Found a workaround for this:

Step 1: Add uuid to entity keys in annotation.

Step 2: Add custom storage handler in annotation.

Step 3: Create the custom storage handler class extending SqlContentEntityStorage with getCustomTableMapping overridden:

/**
 * Use AddUuidTablemaaping to support adding uuid to base table.
 */
public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
  $prefix = $prefix ?: ($this->temporary ? 'tmp_' : '');
  return AddUuidTableMapping::create($entity_type, $storage_definitions, $prefix);
}

Step 4: Create AddUuidTableMapping extending DefaultTableMapping and override create method.

public static function create(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
  $table_mapping = new static($entity_type, $storage_definitions, $prefix);

  $revisionable = $entity_type->isRevisionable();
  $translatable = $entity_type->isTranslatable();

  $id_key = $entity_type->getKey('id');
  $revision_key = $entity_type->getKey('revision');
  $bundle_key = $entity_type->getKey('bundle');
  $uuid_key = $entity_type->getKey('uuid');
  $langcode_key = $entity_type->getKey('langcode');

+ // The Magic, without this uuid will be added to dataTable if such exists.
+ if (array_key_exists('uuid', $storage_definitions)) {
+   $uuid_key = 'uuid';
+ }
+ // end Magic.

  $shared_table_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
    return $table_mapping->allowsSharedTableStorage($definition);
  });

  $key_fields = array_values(
    array_filter(
      [$id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key]
    )
  );

  $all_fields = array_keys($shared_table_definitions);
  $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
    return $definition->isRevisionable();
  }));
  // Make sure the key fields come first in the list of fields.
  $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));

  ...
}

Step 5: Create the field storage definition in post update hook:

$definition_update_manager = \Drupal::entityDefinitionUpdateManager();

/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
$field_storage_definition = BaseFieldDefinition::create('uuid')
  ->setLabel(new TranslatableMarkup('UUID'))
  ->setReadOnly(TRUE);

$definition_update_manager->installFieldStorageDefinition('uuid', $entity_type_id, 'my_custom_module', $field_storage_definition);

Step 6: Set uuid values for existing entities in baseTable:

$rows = $db_connection->select($entity_type_id, 'e')
  ->fields('e', ['id'])
  ->isNull('uuid')
  ->execute()
  ->fetchAll();

foreach ($rows as $row) {
  $db_connection->update($entity_type_id)
    ->fields(['uuid' => \Drupal::service('uuid')->generate()])
    ->condition('id', $row->id)
    ->execute();
}

Step 7: Update entity type and field storage schema:

$definition_update_manager = \Drupal::entityDefinitionUpdateManager();

$e = $definition_update_manager->getEntityType($entity_type_id);
$keys = $e->getKeys();
$keys['uuid'] = 'uuid';
$e->set('entity_keys', $keys);

$definition_update_manager->updateEntityType($e);
$field_storage_definition = $definition_update_manager->getFieldStorageDefinition('uuid', $entity_type_id);
$definition_update_manager->updateFieldStorageDefinition($field_storage_definition);

This last step will update the entity type definition in db + make uuid field not_null in in baseTable.

Done.

Alternate solution is to use $definition_update_manager->updateFieldableEntityType, but will require overriding storage_schema handler(assuming there are existing entities) for the entity type as it forces not_null for entity keys by default.

Your Answer

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

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