template_from_string(), plus de 80 000 fichiers, et un dossier que rien ne purge
Also available in English.
Cet article est nĂ© dâune digression. Pendant lâincident racontĂ© dans 14 minutes de cache:warmup sur EFS, il fallait aussi supprimer un template prĂ©cis dans le cache Twig (var/cache/prod/twig/). En le cherchant je suis tombĂ© sur un dossier de 9 Go.
$ du -sh var/cache/prod/twig
9G var/cache/prod/twigCâest beaucoup de noix ça !
Une recherche dans ce dossier a rĂ©vĂ©lĂ© des dizaines de milliers dâentrĂ©es __string_template__.
$ grep -lr '__string_template__' ./var/cache/prod/twig/ 2>/dev/null | wc -l
82173Premier réflexe : chercher un createTemplate() maison, dans le code custom.
Il nây en a pas. Aucun module ne fait ça cĂŽtĂ© Twig. Les seuls createTemplate() du pĂ©rimĂštre custom sont des appels Smarty, qui alimentent var/cache/*/smarty, pas var/cache/*/twig. Hors de cause.
Le vrai coupable est le colonel moutarde cĆur de PrestaShop 8 lui-mĂȘme.
Le mécanisme : template_from_string() sur chaque page
Chaque page du back-office basée sur un AdminController legacy (la quasi-totalité du back-office) passe par layout.html.twig, qui ouvre sur :
{% extends(template_from_string(
getLegacyLayout(
app.request.attributes.get('_legacy_controller'),
layoutTitle is defined ? layoutTitle : '',
...
)
)) %}getLegacyLayout() ne se contente pas dâinjecter quelques variables. Elle rĂ©cupĂšre le layout legacy complet, rendu cĂŽtĂ© Smarty, titre de page, fil dâAriane, boutons de toolbar avec leurs tokens, lien dâaide, URL admin incluse.
Elle dĂ©coupe ce HTML autour du marqueur {$content}, puis rĂ©encode lâen-tĂȘte et le pied en une sĂ©rie de littĂ©raux Twig via escapeSmarty(). Twig impose une limite dure de 8191 caractĂšres (2^13 - 1) par littĂ©ral ; PrestaShop dĂ©coupe en blocs de 2000 pour rester loin en dessous. Tout ce HTML devient lâargument de template_from_string().
Or ce layout nâest pas le mĂȘme dâune page Ă lâautre. Il embarque le titre de la page, le fil dâAriane, des boutons dont les URL contiennent un token de sĂ©curitĂ©, et ces Ă©lĂ©ments changent selon la page et lâenregistrement affichĂ©s. Le HTML obtenu est donc rarement identique dâune requĂȘte Ă lâautre.
Et Twig nomme un template créé Ă partir dâune chaĂźne dâaprĂšs lâempreinte de cette chaĂźne entiĂšre : createTemplate() produit __string_template__ suivi du hash du texte. Un seul caractĂšre de diffĂ©rence, et câest un autre nom, donc un autre fichier compilĂ© dans le cache.
template_from_string() compile une nouvelle entrée, __string_template__<hash>, à chaque fois. Quasiment jamais réutilisée tellement elle est spécifique.
Ce nâest pas un bug applicatif. Câest le mĂ©canisme qui fait le pont entre lâancien systĂšme de contrĂŽleurs et le rendu Twig moderne, sur une bonne partie du back-office de PrestaShop 8, tel quel.
Et rien ne purge ce dossier tout seul : sans intervention manuelle, ce dossier ne fait que grossir.
Solution
Je ne veux pas modifier le cĆur de PrestaShop pour ça. Le plus simple est de nettoyer rĂ©guliĂšrement le dossier de cache avec un cron journalier.
find var/cache/prod/twig -type f -mtime +7 -deleteVider var/cache/prod/twig se fait sans consĂ©quence : un template manquant se recompile Ă la volĂ©e, Ă la prochaine requĂȘte qui en a besoin. Vider le conteneur DI, Ă lâinverse, peut bloquer tout le back-office le temps dâune recompilation : câest lâincident racontĂ© dans lâarticle prĂ©cĂ©dent.
Câest la leçon la moins intuitive de cette sĂ©rie : dans Symfony, « vider le cache » nâest pas une opĂ©ration unique. Câest un ensemble de caches indĂ©pendants, avec des coĂ»ts de reconstruction et des rayons dâimpact qui nâont rien Ă voir les uns avec les autres. Les connaĂźtre sĂ©parĂ©ment, câest ce qui permet de cibler lâintervention au lieu de tout purger Ă lâaveugle.