Convertir une entité de contenu pour la rendre traduisible avec Drupal 8

Une mappe monde

Jusque Drupal 8.7, nous disposions d'une commande drush fort bien utile pour le développement d'entités de contenu, et les mettre à jour au fur et à mesure de leur évolution avec le projet. Cette commande, drush entup ou drush entity-updates, nous permettait de mettre à jour la définition des entités et/ou de leur champs.

Mais pour des raisons d'intégrité de données, difficiles d'assumer pour une commande aussi utile et générique mais manipulant et modifiant le schéma de la base de données, il a été décidé de retirer cette commande du Coeur, pour redonner la responsabilité aux modules de mettre à jour en connaissance de cause leur structure de données.

Bien que cette commande soit désormais disponible au travers d'un module contribué, devel entity upates, il est recommandé de l'utiliser à des fins de développements uniquement, et non sur un projet en production, et donc avec des données. Les raisons sont très bien détaillées sur la page de ce module et également sur le registre des modifications idoine : Support for automatic entity updates has been removed qui nous fournit d'ailleurs bon nombre d'exemples pour procéder en toute sécurité à ces mises à jour d'entité.

Mais la conversion d'une entité de contenu pour la rendre traduisible ne fait pas partie des exemples donnés. Après avoir chercher la "bonne recette" (vive les tests) pendant quelque temps, essayons de les compléter.

Pour rendre traduisible une entité de contenu, nous devons d'une part modifier son annotation, et la déclaration de ses champs de base (si nécessaire), puis appliquer la mise à jour de la base de données en conséquence.

Une simple entité de contenu, my_entity, non traduisible, ne déclare dans ses annotations que sa base_table :

* base_table = "my_entity",

Tandis que pour qu'une entité de contenu soit traduisible, celle-ci doit le déclarer explicitement, et déclarer également une data_table (une entité de contenu traduisible s'appuie sur deux tables en base de données):

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

Il faut également déclarer les champs qui seront désormais traduisibles

Par exemple pour le champ title, nous rajoutons la propriété translatable avec la méthode setTranslatable(TRUE)

$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);

 

Une fois ces pré-requis faits, il ne reste plus qu'à écrire une fonction de mise à jour. Attention il est impératif d'écrire cette fonction dans un hook_post_update_NAME, fonction qui sera présente dans le fichier MY_MODULE.post-update.php à créer à la racine de votre module.

Voici la fonction en 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);
}

Cette fonction de mise à jour, outre qu'elle va modifier la structure des données de l'entité de contenu va également procéder à la migration des données déjà disponibles. Pour un projet dont le développement commence, sans données donc, il peut être beaucoup plus facile de désinstaller le module puis le ré-installer, ce qui aura pour effet d'installer à nouveau l'entité de contenu avec ses bonnes définitions, ou encore de recourir à la commande drush entity-updates. Mais pour un projet bien avancé, pour lequel des données ont déjà été saisis cela peut s'avérer plus complexe et l'écriture de cette fonction de mise à jour par un développeur Drupal devient indispensable pour sécuriser le processus sur le site en production.

Pour conclure, il vous faudra veiller à mettre à jour les vues basées sur cette entité, si elles existent. Notamment, il vous suffira de changer dans le fichier de configuration YAML de la vue, l'entrée base_table: my_entity par base_table: my_entity_field_data, à la fois pour la table de base de la vue, et également pour tous les champs de base de cette entité utilisés dans la vue.

Et surtout, surtout, n'oubliez pas de sauvegarder avant.

 

Ajouter un commentaire