Drupal, renommer un champ CCK

CCK, comme tout drupalien le sait, permet d'étendre un type de contenu en ajoutant toute sorte de champs (dates, textes, images, liens, etc.). Ces champs portent un "nom machine" (field_XXX) utilisé comme nom de table ou de colonne dans la base de données, et bien évidement dans le code (généralement du thème).

Tout cela est très bien sauf qu'au bout d'un temps, entre les fautes de frappe, les modifications de spécifications, les nouvelles fonctionnalités, le nommage de ces champs peut manquer cruellement de cohérence. Et étrange, vous avez dit étrange, il n'existe dans CCK aucun moyen de les renommer.

Mais quelle idée ?!?

Lorsque je me suis renseigné pour savoir pourquoi manquait une fonctionnalité aussi basique, la réponse m'a assise par terre : "à cause de Views". Plus clairement, si les champs CCK sont utilisés dans une tripotée de vues, les renommer impliqueraient de les toutes les retoucher une à une. Voilà qui n'a pas vraiment amélioré mon appréciation de Views dans le cadre d'un site réalisés par des professionnels (à contrario d'une utilisation amateur de Drupal où views est très utile), mais ceci est une autre histoire.

L'autre raison qui m'a été évoqué est qu'un nom machine n'est qu'un nom machine, et qu'il n'a que besoin d'être unique pur une installation Drupal donnée. Un argument que je peux entendre même si pour moi la refactorisation peut _aussi_ s'appliquer dans le monde web pour obtenir un code plus clair et plus lisible.

Une autre raison qui rendait le renommage "peu utile" aurait sans doute été la faible taille des noms de champ, 32 caractères. Lorsque l'on sait que l'on perd déjà 6 caractères d'emblée (le préfixe "field_" dont je n'ai jamais compris l'intérêt), cela ne laisse pas grand chose pour faire un nommage propre (personnellement j'utilise field_{nom du type}_{nom du champ}). Une limitation qui doit dater d'un ancien temps car aujourd'hui pour MySQL et comme PostgreSQL, la taille maximum commune pour un nom de colonne est de 63 caractères. D'ailleurs si certains sont intéressés, j'ai fais un patch pour (je l'espère) corriger cela.

Bref, plein de raisons pour pas toucher au noms de champs et donc de ne pas implémenter dans CCK un système de renommage. Et de mon point de vue autant de raison de malgré tout vouloir garder un code propre et de le faire quant même...

Structure des champs CCK

Pour renommer un champ CCK, il faut un peu en comprendre la structure. Un champ est défini, entre autre, par son nom, son type et sa cardinalité (en langage CCK,c'est le paramètre "Number of Values").

  • Si la cardinalité est de type 1:1 (Une seule valeur par champ), les données de ce champ seront stockées dans une table commune à tous les champs de cardinalité 1:1 d'un type de contenu donné nommée content_type_{nom du type de contenu}.
  • Si la cardinalité est différente de 1:1 (de 2 à Multiples valeurs en CCK), les valeurs du champ sont stockées dans une table autonome nommée content_field_{nom du champ}.

Dans un cas comme dans l'autre, CCK va créer dans le table content_type_{nom du type de contenu} ou content_field_{nom du champ} les mêmes champs pour stocker les valeurs. La seule différence est que la table content_field_{nom du champ} disposera en plus d'un champ delta qui pour un même node (couple nid, vid) permet de différencier les N valeurs.

Reste les colonnes dédiés à la valeur du champ. Il peut y en avoir une seule, comme pour un type simple (entier ou chaîne sans formatage), jusqu'à un nombre illimité (par exemple 3 pour un champ de type filefield).

Processus de renommage

Il est entendu que vous devez sauvegarder votre base de données avant de jouer avec tout ce qui suit. Et tester intensivement vos modifications pour valider que rien n'a été cassé. Je ne saurais être tenu responsable si vous massacrez votre site à 2 jours de la mise en production ;-)

Le processus de renommage commence par déterminer sur quelle cardinalité se place le champ à renommer. Si c'est une cardinalité différente de 1:1, il nous faudra commencer par renommer la table dédiée :

ALTER TABLE content_field_nom_moche RENAME TO content_field_nom_joli
renommage d'une table/champ en PostgreSQL

La suite du processus est la même pour les deux types de stockage. Elle consiste tout d'abord à renommer les colonnes portant les valeurs du champ. Le nom de ces colonnes débutent tous par field_{nom du champ} suivi d'un caractère souligné et d'un identifiant (value pour un entier ou une chaîne, fid, list et data pour un champ image field, nid pour un champ nodereference, etc.). Le renommage de ces colonnes se fait sur la table content_type_{nom du type} pour un champ de cadinalité 1:1 ou sur la table que nous venons de renommer dans le cas contraire :

ALTER TABLE content_field_nom_joli RENAME COLUMN field_nom_moche_fid TO field_nom_joli_fid;
ALTER TABLE content_field_nom_joli RENAME COLUMN field_nom_moche_list TO field_nom_joli_list;
ALTER TABLE content_field_nom_joli RENAME COLUMN field_nom_moche_data TO field_nom_joli_data;
Renommage des valeurs d'un champ fielfield

Dernière étape, elle aussi commune aux deux types de stockages, prévenir CCK du nouveau nommage. Cela consiste à remplacer l'ancien nom par le nouveau dans les tables content_node_field (la définition CCK d'un champ), content_node_field_instance (la définition CCK d'une instance de champ) et dans content_group_fields (la définition CCK d'un groupe de champs qui peut potentiellement contenir notre champ).

UPDATE content_node_field SET field_name='field_nom_joli' WHERE field_name='field_nom_moche'
UPDATE content_node_field_instance SET field_name='field_nom_joli' WHERE field_name='field_nom_moche'
UPDATE content_group_field SET field_name='field_nom_joli' WHERE field_name='field_nom_moche'
Modification des définitions de champ CCK

Et voilà, c'est terminé.

Automatisation

Personnellement je me suis construit un petit module qui fait le travail à ma place car la fois où j'ai eu une centaines de champs à renommer, je n'allais pas faire tout cela à la main. Le module je le garde pour moi car je n'ai aucune envie d'en faire la maintenance :-) En revanche, voici sa procédure centrale pour PostgreSQL (à adapter pour MySQL).

function cck_rename_field($field_name, $new_name) {
  // On demande à CCK la liste des champs
  $fields = content_fields();

  // On récupère notre champ à renommer
  $field = $fields[$field_name];

  $content_type = content_types($field['type_name']);

  // un $field['db_storage'] à TRUE indique une cardinalité 1:1
  if ($field['db_storage']) {
    // Stockage par colonne : Sélection de la table à modifier
    $table = "content_type_";
  }
  else {
    // Stockage par table : Renommage de la table
    $old_table = "content_";
    $table = "content_";
    $query = "ALTER TABLE {} RENAME TO {}";
    $result = db_query($query);
    if (!$result) {
      drupal_set_message(t("Unable to execute @query, sorry...", array('@query' => $query)));
        return;
    }
  }

  // On itére sur chaque colonne de stockage du champ
  foreach ($field['columns'] as $column => $data) {
    // Renommage de la colonne
    $query = "ALTER TABLE {} RENAME COLUMN _ TO _";
    $result = db_query($query);
    if (!$result) {
       drupal_set_message(t("Unable to execute @query, sorry...", array('@query' => $query)));
       return;
    }
  }

  // On itére sur les 3 tables de définition de CCK pour change
  // le nom du champ
  foreach (array('content_node_field','content_node_field_instance','content_group_fields') as $table) {
    $query = "UPDATE {} SET field_name='$new_name' WHERE field_name='$field_name'";
    $result = db_query($query);
    if (!$result) {
       drupal_set_message(t("Unable to update @table, sorry...", array('@table' => $table)));
       return;
    }
  }

  // Ménage pour vider les caches CCK
  content_clear_type_cache();
}
procédure de renommage de champ

La procédure a été testé sur PostgreSQL 8.4, CCK 2.8 et mon patch pour atteindre les 63 caractères, mais soyez prudent quant à son utilisation, toujours faire des sauvegardes avant.

Conclusion

La conclusion de cette histoire serait sans doute un réflexion sérieuse sur le concept même de "nom machine". D'un point de vue pratique le besoin est réel. Il s'agit d'avoir pour des objets système (Champ, Context, Bloc, etc.) un identifiant unique pour une installation donnée et le même pour chaque machine où cette installation est répliquée. L'autre besoin réel est de disposer dans le code PHP d'identifiant, choisi par de développeur, permettant d'atteindre ces objets systèmes.

Là où l'approche adoptée par les modules (CCK, Context, Features, etc.) pose soucis est dans la résolution des deux problématiques en une seule passe. C'est à dire en laissant le développeur choisir un identifiant unique. Du coup tout modification de l'identifiant implique de nombreuse conséquence plus ou moins contrôlables, d'où le bridage posé par CCK interdisant son renommage. En même temps cet identifiant étant un aussi le nom "publique" de l'objet système utilisé dans notre code, empêcher son changement est aussi ridicule que de vous interdire de renommer une fonction ou une variable sous prétexte qu'elle est utilisée ailleurs.

Une solution possible pourrait consister à traiter les deux notions de manière autonome. Drupal pourrait proposer un SystemObjectAPI, permettant aux modules d'enregistrer des classes d'objets (champ, bloc, etc.) et des instances d'objet (champ XXX). L'API, lors de l'enregistrement d'une instance, créerait un GUID (un identifiant unique à sa génération dans tous les univers que peut atteindre l'USS Entreprise) qui serait utilisé par un module (ex. CCK), et par les modules qui utilisent les objets systèmes de ce module (ex. Views) pour disposer d'une référence stable et facile à mettre en cache.

Cette API pourrait aussi fournir le moyen d'assigner un identifiant PHP à chaque instance d'objet système. Les modules utiliserait donc cet identifiant lorsqu'il s'agirait d'interagir avec le reste du monde. Dans le cas de CCK, ces identifiants seraient utilisés pour convertir les GUID en champs correspondant de l'objet Node.

Une telle API ne serait pas limité aux seuls identifiants PHP. Nous pourrions aussi imaginer l'utiliser pour définir les identifiants SQL utilisés pour les tables et colonnes. Ainsi chaque objet système pourrait avoir un nom machine fiable (le GUID), un identifiant PHP facile et joli à regarder, et un identifiant SQL qui ne pourrirait pas le schéma de base de données.

Vus : 1064
Publié par arNuméral : 54