Dans le volet précédent de la saga "Comment changer dynamiquement de thème Drupal, notre module souffrait d'un défaut de taille : Il fallait désactiver le cache pour qu'il fonctionne. Nous allons voir maintenant comment le modifier pour que la même chose soit possible sans perte de performances.
Le cache de page sous Drupal est un mécanisme bête et efficace. S'il est actif, on commence avant d'afficher quoi que ce soit, par vérifier qu'il n'existe pas un enregistrement correspondant à l'URL demandée dans la table cache_page. Si c'est le cas, les données de cet enregistrement sont directement expédiées sur le navigateur client et le traitement s'arrête là. Le cache de page contient donc toute la page, thème compris. Et c'est bien là notre problème.
Ce cache peut être soit désactivé, soit en mode "normal", soit en mode "agressif". En mode agressif, aucun module ne peut intervenir et le site est quasiment "statique". C'est pour cela qu'il est assez rarement utilisé. En mode normal, certains modules dits "bootstrap" (voir l'article précédent) peuvent avoir une action sur le contenu, c'est ce que nous allons exploiter.
Bien évidement, une page mise en cache n'est pas valide à vie. Il y a un paramètre que vous pouvez modifier dans les performances indiquant de durée de validité des enregistrements. Passé ce temps, la page est refabriquée et vient écraser l'ancienne version en base.
Un autre aspect important à connaître sur ce cache de page, c'est qu'il est désactivé pour tout utilisateur connecté. Ce qui peut poser un problème de performance dans le cas où votre site n’a que des utilisateurs authentifiés. En gros, le cache n'agit que sur la version anonyme du site et c'est un aspect à ne pas négliger car lorsque l'on développe, nous sommes souvent connectés, et le comportement n'est plus du tout le même...
Donc pour créer un thème dynamique en fonction du navigateur cible, avec un cache en mode "normal", on va tomber sur le problème du "premier qui gagne". Si un navigateur IE6 passe avant les autres, c'est le thème IE6 qui va finir dans le cache et tout le monde sera logé à la même enseigne. Pire, vu qu'il y a un enregistrement par page, et donc à la rubrique/billet affiché, on aurait rapidement un site "arlequin" avec un thème qui change de manière aléatoire d'une page à l'autre.
La seule solution pour régler ce problème est donc de modifier la stratégie de gestion du cache.
Malheureusement il n'y a pas de hooks que l'on puisse implémenter pour changer la manière dont fonctionne le cache. Il va donc nous falloir modifier les sources du coeur de Drupal. Prenez donc soin d'en faire une copie préalable.
Le principe général est assez simple. Drupal retrouve le bon enregistrement d'une page dans le cache en fonction d'une clef. Cette clef est simplement l'URL de la page à afficher. Il va donc nous falloir changer un peu cela pour que la nouvelle clef soit cette fois l'URL additionnée du thème $custom_theme.
La stratégie du cache de page est pris en charge dans le module includes/bootstrap.inc, dans la fonction _drupal_cache_init. C'est là que sont appelés nos kooks _init et _exit. Et c'est aussi là qu'est fait la distinction entre les trois modes du cache.
Le problème est que le hook _init est appelé après récupération de la page en cache. Et du coup notre clef va être fausse. Il faut donc modifier la fonction pour faire appel à notre module avant que le cache ne soit lu. Malheureusement il n'existe pas de fonction dans l'API de cache pour vérifier qu'un enregistrement existe avant de le charger effectivement. Et si l'on continue d'utiliser le hook _init, on prend le risque qu'il soit appelé deux fois de suite : une fois avant la lecture en cache, et une fois de plus s'il n'y avait pas d'enregistrement en cache pour cette page. Ce n'est pas un problème pour notre module mais cela risque d'en devenir un pour les autres modules "bootstrap" utilisant init/exit. Nous allons donc éviter le problème en créant notre propre hook, _preinit, et en l'invoquant avant la récupération des données en cache. Cela nous donne la fonction de remplacement suivante :
Ensuite, nous allons modifier le module de l'article précédent pour renommer _init en _preinit, et créer une nouvelle fonction _init qui sera cette fois vide.
Maintenant nous allons modifier les fonctions qui lisent et qui écrivent dans le cache pour ajouter le thème dans la clef. La lecture tout d'abord, se trouve dans includes/bootstrap.inc, fonction page_get_cache. La modification est enfantine, elle consiste juste à déclarer en global notre variable $custom_theme et ajouter cette référence dans la clef de lecture du cache :
Enfin, il ne nous reste plus qu'à modifier la fonction de sauvegarde de la page, qui elle se trouve dans includes/common.inc, fonction page_set_cache. Et c'est le même type d'altération :
Et voilà, c'est terminé. Il ne reste plus qu'à vider le "vieux" cache en passant par votre base de donnée et en exécutant un delete from cache_page. Maintenant, vous pouvez ré-activer le cache (en mode normal, pas agressif !!) et tester. En base de donnée, un select cid from page_cache vous confirme que les nouvelles clefs sont prise en compte.
Outre une meilleure compréhension du système de cache de Drupal, nous avons maintenant un module de changement dynamique de thème qui fonctionne parfaitement avec le cache, sans perte de performances. Le seul impacte est donc un doublement du volume du cache en base, ce qui n'est pas un problème en soit. Alors certes cela s'est fait au prix d'une modification mineure du Coeur mais le résultat est là. Maintenant il est toujours possible de rendre cette modification plus générique en introduisant un hook _get_cache_page_key qui prendrait en paramètre la clef d'origine et qui renverrait la clef modifiée. Cela réduirait considérablement l'impacte sur le coeur de Drupal. Pour une prochaine fois peut-être
Poster un nouveau commentaire