Utilisation de plugins de processus pour la transformation de données dans les migrations Drupal

Dans l’entrée précédente, nous avons écrit notre première migration Drupal. Dans cet exemple, nous avons copié les valeurs mot à mot de la source vers la destination. D’habitude, les données doivent être transformées d’une manière ou d’une autre pour correspondre au format attendu par la destination ou pour répondre aux besoins métier. Aujourd’hui, nous allons en apprendre plus sur les plugins de processus et comment ils fonctionnent dans le cadre de le pipeline de migration Drupal.

Syntaxe pour la définition et le chaînage du plug-in de processus

Sucre syntactique

L’API Migrate offre beaucoup de sucre syntaxique pour faciliter l’écriture des fichiers de définition de migration. Les mappages de champs dans la section processus en sont un exemple. Chacun d’entre eux nécessite la définition d’un plugin de processus. Si aucun n’est défini manuellement, alors le plugin `get` est choisi par défaut. Les deux extraits de code suivants sont équivalents en fonctionnalités.

process:
  title: creative_title
process:
  title:
    plugin: get
    source: creative_title

Le plugin de processus `get` copie simplement une valeur de la source vers la destination sans faire aucun changement. Comme il s’agit d’une opération courante, `get` est considéré comme la valeur par défaut. Il existe de nombreux plugins de processus fournis par le cœur Drupal et les modules contribués. Leur configuration peut être généralisée comme suit :

process:
  destination_field:
    plugin: plugin_name
    config_1: value_1
    config_2: value_2
    config_3: value_3

Le plugin de processus est configuré dans un niveau supplémentaire d’indentation sous le champ de destination. La clé `plugin` est nécessaire et détermine quel plugin utiliser. Ensuite, une liste d’options de configuration suit. Consultez la documentation de chaque plugin pour connaître les options disponibles. Certaines options de configuration seront requises tandis que d’autres seront optionnelles. Par exemple, le plugin `concat` nécessite une `source`, mais le `delimiter` est facultatif. Un exemple d’utilisation puis dans cet article.

Fourniture des valeurs par défaut

Parfois, la destination exige qu’une propriété ou un champ soit défini, mais cette information n’est pas présente dans la source. Imaginez que vous migrez des nœuds. Comme nous l’avons mentionné, il est recommandé d’écrire un fichier de migration par type de contenu. Si vous savez à l’avance que pour une migration particulière vous créerez toujours des nœuds de type `Basic page`, alors il serait redondant d’avoir une colonne dans la source avec la même valeur pour chaque ligne. Les données peuvent ne pas être nécessaires. Ou peut-être qu’il n’existe pas. Dans tous les cas, le plugin `default_value` peut être utilisé pour fournir une valeur lorsque les données ne sont pas disponibles dans le source.

source: ...
process:
  type:
    plugin: default_value
    default_value: page
destination:
  plugin: 'entity:node'

L’exemple ci-dessus définit la propriété `type’; pour tous les nœuds de cette migration sur `page’;, qui est le nom de machine du type de contenu `Basic page`. Ne confondez pas le nom du plugin avec le nom de sa propriété de configuration car ils sont identiques : `default_value`. Notez également qu’étant donné qu’un type (contenu) est défini manuellement dans la section process, la clé `default_bundle` dans la section destination n’est plus nécessaire. Vous pouvez voir ce dernier être utilisé dans l’exemple du billet de blog sur l’écriture d’une migration Drupal.

Concaténation des valeurs

Considérez la demande de migration suivante : vous avez une liste de personnes avec le prénom et le nom de famille dans des colonnes séparées. Les deux sont capitalisés. Les deux valeurs doivent être mises ensemble (concaténées) et utilisées comme titre des noeuds de type `Basic page`. Le corps de caractère doit être modifié de façon à ce que seule la première lettre de chaque mot soit en majuscule. S’il est nécessaire de les afficher en majuscules, le CSS peut être utilisé pour la présentation. Par exemple : `FELIX DELATTRE` serait transformé en `Felix Delattre`.

Conseil: Remettre en question les exigences opérationnelles lorsqu’elles peuvent produire des résultats non désirés. Par exemple, si vous deviez implémenter cette fonctionnalité comme demandé, `DAMIEN MCKENNA` serait transformé en `Damien Mckenna`. Ce n’est pas la majuscule correcte pour le nom de famille `McKenna`. Si la transformation automatique n’est pas possible ou faisable pour toutes les variations des données source, prenez des notes et effectuez des mises à jour manuelles après la migration initiale. Évaluer autant de cas d’utilisation que possible et les porter à l’attention du client.

Pour implémenter cette fonctionnalité, créons un nouveau module `ud_migrations_process_intro`, créons un dossier `migrations`, et écrivons un fichier de définition de migration appelé `udm_process_intro.yml` dedans. Suivez les instructions de ce post pour trouver l’emplacement et la structure de dossier appropriés ou téléchargez le module exemple à partir de https://github.com/dinarcon/ud_migrations. C’est celui nommé `UD Process Plugins Introduction` et le nom de machine `udm_process_intro`. Pour cet exemple, nous supposons une installation Drupal utilisant le profil d’installation `standard` qui est fourni avec le type de contenu `Basic Page`. Voyons comment gérer la concaténation du prénom et du nom de famille.

id: udm_process_intro
label: 'UD Process Plugins Introduction'
source:
  plugin: embedded_data
  data_rows:
    -
      unique_id: 1
      first_name: 'FELIX'
      last_name: 'DELATTRE'
    -
      unique_id: 2
      first_name: 'BENJAMIN'
      last_name: 'MELANÇON'
    -
      unique_id: 3
      first_name: 'STEFAN'
      last_name: 'FREUDENBERG'
  ids:
    unique_id:
      type: integer
process:
  type:
    plugin: default_value
    default_value: page
  title:
    plugin: concat
    source:
      - first_name
      - last_name
    delimiter: ' '
destination:
  plugin: 'entity:node'

Le plugin `concat‘; peut être utilisé pour coller un nombre arbitraire de chaînes de caractères. Sa propriété `source` contient un tableau de toutes les valeurs que vous voulez mettre ensemble. Le `delimiter` est un paramètre optionnel qui définit une chaîne à ajouter entre les éléments lorsqu’ils sont concaténés. S’il n’est pas défini, il n’y aura pas de séparation entre les éléments dans le résultat concaténé. Ce plugin a une limitation importante. Vous ne pouvez pas utiliser des chaînes de caractères littéraux (string literals) comme partie de ce que vous voulez concaténer. Par exemple, joignant la chaîne `Hello` à la valeur de la colonne `first_name`. Toutes les valeurs à concaténer doivent être des colonnes dans la source ou des zones déjà disponibles dans le pipeline du processus. Nous en parlerons dans un prochain billet de blog.

Pour exécuter la migration ci-dessus, vous devez activer le module `ud_migrations_process_intro`. En supposant que vous avez installé `Migrate Run`, ouvrez un terminal, changez les répertoires à votre `docroot` Drupal, et exécutez la commande suivante : `drush migrate:import udm_process_intro`. Consultez ce billet si la migration échoue. Si ça marche, vous verrez trois pages de base dont le titre contient les noms de certains de mes mentors Drupal#DrupalThanks

Chaînage de plugins de processus

Des progrès satisfaisants ont été réalisés jusqu’à présent, mais la fonctionnalité n’a pas encore été entièrement mise en œuvre. Vous devez encore changer la majuscule pour que seule la première lettre de chaque mot du titre résultant soit en majuscules. Heureusement, l’API Migrate permet de chaîner les plugins de processus. Cela fonctionne de la même manière que les pipelines unix en ce sens que la sortie d’un plugin de processus devient l’entrée du plugin suivant dans la chaîne. Lorsque le dernier plugin de la chaîne termine sa transformation, la valeur de retour est attribuée au champ de destination. Voyons ça en action :

id: udm_process_intro
label: 'UD Process Plugins Introduction'
source: ...
process:
  type: ...
  title:
    -
      plugin: concat
      source:
        - first_name
        - last_name
      delimiter: ' '
    -
      plugin: callback
      callable: mb_strtolower
    -
      plugin: callback
      callable: ucwords
destination: ...

Le plugin de processus `callback` passe une valeur à une fonction PHP et retourne son résultat. La fonction à appeler est spécifiée dans l’option de configuration `callable`. Notez que ce plugin attend une option `source` contenant une colonne de la source ou de la valeur du pipeline de processus. Cette valeur est envoyée comme premier argument à la fonction. Parce que nous utilisons le plugin `callback` comme partie d’une chaîne, la source est supposée être la dernière sortie du plugin précédent. Il n’est donc pas nécessaire de définir une `source;. Ainsi, nous concaténons les colonnes, les mettons toutes en minuscules, puis capitalisons chaque mot.

S’appuyer sur des appels directs de fonctions PHP ne devrait être qu’un dernier recours. De meilleures alternatives incluent l’écriture de vos propres plugins de processus qui encapsule votre logique séparément de la définition de la migration. Le plugin `callback` a ses propres limitations. Par exemple, vous ne pouvez pas passer de paramètres supplémentaires à la fonction `callable`. Il recevra la valeur spécifiée comme premier argument et rien d’autre. Dans l’exemple ci-dessus, nous pourrions combiner les appels à mb_strtolower() et ucwords() en un seul appel à mb_convert_case($source, MB_CASE_TITLE) si les paramètres supplémentaires étaient autorisés.

Conseil : Vous devriez avoir une bonne compréhension de vos formats de source et de destination. Dans cet exemple, une des valeurs à vouloir transformer est `MELANÇON`. A cause de la cédille (ç), l’utilisation de strtolower() n’est pas adéquate dans ce cas car elle laisserait ce caractère en majuscules (`melanÇon`). Les fonctions de chaînes de caractères multioctets (mb_*) sont nécessaires pour une transformation correcte. ucwords() n’en fait pas partie et présenterait des problèmes similaires si la première lettre des mots est un caractère spécial. Une attention particulière doit être portée à l’encodage des caractères des tables dans votre base de données de destination.

Note technique: `mb_strtolower` est une fonction fournie par l’extension PHP `mbstring`. Il n’est pas activé par défaut ou vous ne l’avez peut-être pas installé. Dans ces cas, la fonction ne serait pas disponible lorsque Drupal essaie de l’appeler. L’erreur suivante se produit lorsque vous essayez d’appeler une fonction qui n’est pas disponible : Le `callable` doit être une fonction ou méthode valide. Pour Drupal et cette fonction particulière, cette erreur ne serait jamais déclenchée, même si l’extension est manquante. C’est parce que le cœur Drupal dépend de certains paquets Symfony qui dépendent du paquet `symfony/polyfill-mbstring`. Ce dernier fournit un polyfill pour les fonctions mb_* qui a été utilisé depuis la version 8.6.x de Drupal.

Qu’avez-vous appris dans le billet d’aujourd’hui ? Saviez-vous que le sucre syntaxique vous permet d’écrire des définitions de plugins plus courtes ? Etiez-vous au courant de l’enchaînement de plugins de processus pour effectuer plusieurs transformations sur les mêmes données ? Lors de la planification de vos migrations, aviez-vous pris en compte l’encodage des caractères sur la source et la destination ? Faites-vous de votre mieux pour éviter le plugin de processus `callback ? Veuillez partager vos réponses dans les commentaires. Aussi, je vous serais reconnaissant de bien vouloir partager ce billet de blog avec vos collègues.