Converting a content entity type to make it translatable with Drupal 8

A world map

Until Drupal 8.7, we had a drush command that was very useful for developing content entities, and updating them as they evolved with the project. This command, drush entup or drush entity-updates, allowed us to update the definition of the entities and/or their fields.

But for reasons of data integrity, difficult to assume for such a useful and generic command but manipulating and modifying the database schema, it was decided to remove this command from the Core, to give back the responsibility to the modules to update their data structure with full knowledge of the facts.

Although this command is now available through a contributed module, devel entity upates, it is recommended to use it for development purposes only, and not on a project in production, and therefore with data. The reasons are very well detailed on the page of this module and also on the appropriate change record: Support for automatic entity updates has been removed which provides us with many examples to safely perform these entity updates.

But converting a content entity type to make it translatable is not one of the examples given. After searching for the "right recipe" (long live to the tests) for some time, let's try to complete them.

To make a content entity translatable, we must first modify its annotation, and the declaration of its base fields (if necessary), and then apply the database update accordingly.

A simple content entity, my_entity, not translatable, only declares in its annotations its base_table:

* base_table = "my_entity",

While for a content entity to be translatable, it must explicitly declare it, and also declare a data_table (a translatable content entity relies on two database tables) :

*   translatable = TRUE,
*   base_table = "my_entity",
*   data_table = "my_entity_field_data",

It is also necessary to declare the fields that will now be translatable.

For example, for the title field, we add the translatable property with the setTranslatable(TRUE) method

$fields['title'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Name'))
      ->setDescription(t('The name of the Ranges entity entity.'))
      ->setTranslatable(TRUE)
      ->setSettings([
        'max_length' => 255,
        'text_processing' => 0,
      ])
      ->setDefaultValue('')
      ->setDisplayOptions('view', [
        'label' => 'above',
        'type' => 'string',
        'weight' => -4,
      ])
      ->setDisplayOptions('form', [
        'type' => 'string_textfield',
        'weight' => -4,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE)
      ->setRequired(TRUE);

 

Once these prerequisites have been met, all that remains is to write an update function. Be careful. It is imperative to write this function in a hook_post_update_NAME, a function that will be present in the MY_MODULE.post_update.php file to be created at the root of your module.

This is the function in question.

/**
 * Make the my_entity entity type translatable.
 */
function my_module_post_update_1(&$sandbox) {
  // Here we update the entity type.
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $definition_update_manager->getEntityType('my_entity');
  $entity_type->set('translatable', TRUE);
  $entity_type->set('data_table', 'my_entity_field_data');

  // We need to update the field storage definitions, for the langcode field, and for all
  // the fields we updated on the entity Class. Add here all the fields you updated in the
  // entity Class by adding setTranslatable(TRUE).
  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
  $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('my_entity');
  $field_storage_definitions['title']->setTranslatable(TRUE);
  $field_storage_definitions['langcode']->setTranslatable(TRUE);

  // We need to add a new field, default langcode.
  $storage_definition = BaseFieldDefinition::create('boolean')
    ->setName('default_langcode')
    ->setLabel(t('Default translation'))
    ->setDescription(t('A flag indicating whether this is the default translation.'))
    ->setTargetEntityTypeId('my_entity')
    ->setTargetBundle(NULL)
    ->setTranslatable(TRUE)
    ->setRevisionable(TRUE)
    ->setDefaultValue(TRUE);
  $field_storage_definitions['default_langcode'] = $storage_definition;

  // And now we can launch the process for updating the entity type and the database 
  // schema, including data migration.
  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
}

This update function, in addition to modifying the data structure of the content entity, will also migrate data already available. For a project whose development begins, without data, it may be much easier to uninstall the module and then re-install it, which will have the effect of re-installing the content entity with its correct definitions, or to use the drush entity-updates command. But for a well advanced project, for which data has already been entered, this can be more complex and writing this update function by a Drupal developer becomes essential to secure the process on the production site.

To conclude, you will need to ensure that you update the views based on this entity, if they exist. In particular, you will only have to change in the YAML configuration file of the view (and reimport it after been updated), the entry base_table: my_entity by base_table: my_entity_field_data, both for the base table of the view, and also for all the base fields of this entity used in the view.

And above all, don't forget to save before.

 

Commentaires

Soumis par Didier (non vérifié) le 01/07/2020 à 12:03 - Permalien

Hello, salut.

The filename MY_MODULE.post-update.php should be MY_MODULE.post_update.php, otherwise dash updb won't run the post-update hook.

Attention, le nom du fichier MY_MODULE.post-update.php doit plutôt être MY_MODULE.post_update.php sinon il ne sera pas pris en compte.
Didier

Ajouter un commentaire