Contexts de cache
Mise en cache des contextes = dépendances contextuelles (de la requête)
Les contextes de cache sont similaires à l’en-tête HTTP Vary.
Pourquoi ?
Les contextes de cache définissent la manière de créer des variantes dépendantes du contexte pour tout ce qui doit être mis en cache. Le code qui crée les caches devient plus lisible, et la même logique ne doit pas être répétée partout où des variations contextuelles identiques sont nécessaires.
Exemples :
- La sortie de certaines données dépend du thème actif, avec des résultats différents selon les thèmes. Vous utiliserez alors le cache dépendant du contexte du thème.
- Lors de la création d’un tableau de rendu affichant un message personnalisé, le rendu dépend de l’utilisateur. Le cache dépendra donc du contexte utilisateur.
- En général, lorsque des données coûteuses à calculer dépendent de l’environnement serveur, un contexte de cache est également utilisé.
Comment ?
Un contexte de cache est une chaîne référant à un service de contexte de cache disponible (voir ci-dessous).
Les contextes de cache sont passés sous forme d’ensembles (ordre non significatif) de chaînes (string[]), car un élément de cache peut dépendre de plusieurs contextes.
En général, les contextes sont obtenus depuis l’objet contexte de la requête (i.e. la requête). La plupart des données liées à l’environnement web proviennent du contexte de la requête. Les réponses HTTP sont en grande partie générées selon les propriétés de la requête HTTP initiale.
Cependant, les contextes de cache ne doivent pas forcément provenir de la requête : ils peuvent aussi dépendre de code déployé, comme un contexte deployment_id.
Les contextes de cache sont organisés en hiérarchie. Par exemple, si un élément varie pour chaque utilisateur, il n’est pas suffisant d’utiliser uniquement les permissions, car cela varie selon l’utilisateur. Le cache sera créé pour chaque permission. Si une partie de la page varie par utilisateur, et une autre selon les permissions, Drupal utilise cette hiérarchie pour ne pas créer de variations inutiles.
Syntaxe
- Les points séparent les parents des enfants.
- Un nom au pluriel indique que le contexte accepte un paramètre : ajoutez un deux-points suivi du paramètre souhaité (si absent, tous les paramètres possibles sont pris, ex. toutes les requêtes).
Contextes de cache du cœur Drupal 8
Le cœur Drupal 8 fournit cette hiérarchie :
cookies :name headers :name ip languages :type protocol_version // Depuis 8.9.x ou supérieur. request_format route .book_navigation .menu_active_trails :menu_name .name session .exists theme timezone url .path .is_front // Depuis 8.3.x ou supérieur. .parent .query_args :key .pagers :pager_id .site user .is_super_user .node_grants :operation .permissions .roles :role
Note : Pour utiliser le contexte url.path.is_front dans des versions antérieures, voir cet article de changement.
Cette hiérarchie apporte trois avantages :
- Pas d’ambiguïté : on sait quel contexte parent est référencé, partout où il est utilisé.
- La comparaison et la simplification des contextes de cache sont facilitées : si a.b.c et a.b sont présents, on sait que a.b inclut a.b.c, donc on peut ignorer a.b.c.
- Pas besoin de garantir l’unicité de chaque niveau dans l’arbre.
Exemples de contextes issus de cette hiérarchie :
- theme (dépend du thème actif)
- user.roles (dépend de la combinaison des rôles)
- user.roles:anonymous (dépend si l’utilisateur est anonyme ou non)
- languages (diffère selon le type de langue : interface, contenu…)
- languages:language_interface (dépend de la langue d’interface)
- languages:language_content (dépend de la langue du contenu)
- url (dépend de l’URL entière)
- url.query_args (dépend de la chaîne de requête entière)
- url.query_args:foo (dépend de l’argument foo dans la requête)
- protocol_version (dépend de HTTP 1 contre 2)
Optimisation/simplification des contextes de cache
Drupal optimise automatiquement les contextes grâce à leur hiérarchie. Par exemple, si une partie de la page varie selon l’utilisateur (context user) et une autre selon les permissions (user.permissions), il est inutile de différencier selon user.permissions car user couvre déjà toutes les variations. Ainsi : optimize([user, user.permissions]) = [user].
Cependant, cette optimisation signifie que des changements dans les permissions ne déclenchent pas nécessairement un nouveau cache spécifique à user.permissions, ce qui peut entraîner une certaine inexactitude temporaire.
C’est pourquoi les contextes dépendants d’une configuration volatile utilisent des métadonnées de cache supplémentaires : tags de cache et max-age. Si un contexte optimisé est utilisé, ses tags sont associés au cache, permettant d’invalider le cache lors de changements importants.
Un cas particulier est celui des node grants. Ces permissions sont spécifiques à chaque utilisateur et très dynamiques (parfois variant toutes les minutes). Le contexte user.node_grants est donc défini avec max-age = 0, ce qui empêche son cache d’être optimisé. Ainsi : optimize([user, user.node_grants]) = [user, user.node_grants].
Cependant, certains sites peuvent modifier cela en fixant max-age = 3600, autorisant un cache jusqu’à une heure, et alors : optimize([user, user.node_grants]) = [user].
Reconnaître, définir et créer des contextes de cache
Les contextes de cache sont des services avec le tag cache.context. N’importe quel module peut en ajouter. Ils implémentent \Drupal\Core\Cache\Context\CacheContextInterface ou \Drupal\Core\Cache\Context\CalculatedCacheContextInterface (pour ceux acceptant des paramètres, suffixe :paramètre).
Pour trouver tous les contextes disponibles, consultez les implémentations de ces interfaces via votre IDE (ex. PHPStorm : hiérarchie des types → sous-types). Ou utilisez la console Drupal :
$ drupal debug:cache:context Context ID Label Class path cookies HTTP-Cookies Drupal\Core\Cache\Context\CookiesCacheContext headers HTTP-Header Drupal\Core\Cache\Context\HeadersCacheContext ip IP-Adresse Drupal\Core\Cache\Context\IpCacheContext languages Language Drupal\Core\Cache\Context\LanguagesCacheContext request_format Request Format Drupal\Core\Cache\Context\RequestFormatCacheContext route Route Drupal\Core\Cache\Context\RouteCacheContext route.book_navigation Book Navigation Drupal\book\Cache\BookNavigationCacheContext route.menu_active_trails Active Menu Trails Drupal\Core\Cache\Context\MenuActiveTrailsCacheContext
Dans chaque classe, on trouve ce commentaire :
Cache context ID: 'user'.
Cela signifie que 'user' est le contexte à utiliser dans le code. Vous pouvez aussi chercher l’utilisation dans les fichiers *.services.yml pour voir l’identifiant du service.
Astuce : obtenez la liste complète des contextes de cache en consultant les services tagués cache.context dans le cœur Drupal !
Le nom du service suit toujours ce format : cache_context.parent.contexte, par exemple :
cache_context.route.book_navigation: class: Drupal\book\Cache\BookNavigationCacheContext arguments: ['@request_stack'] tags: - { name: cache.context }
Cela définit le contexte de cache route.book_navigation.
Débogage
Supposons qu’un élément soit mis en cache avec les clés ['foo', 'bar'] et contextes ['languages:language_interface', 'user.permissions', 'route']. Le cache CID sera :
foo:bar:[languages:language_interface]=en:[user.permissions]=A_QUITE_LONG_HASH:[route]=myroute.ROUTE_PARAMS_HASH
Ce qui signifie :
- les clés de cache sont listées en premier, dans l’ordre donné
- les contextes sont listés ensuite, par ordre alphabétique, sous la forme [<nom contexte>]=<valeur contexte>
- toutes les parties sont concaténées par des deux-points
Cela facilite l’analyse et le débogage du cache.
En-têtes (débogage)
Pour voir de quels contextes dépend une réponse, regardez simplement l’en-tête X-Drupal-Cache-Contexts
!
Note : si vous ne voyez pas ces en-têtes, configurez Drupal en mode développement comme indiqué ici.
Cache dynamique de page
L’utilisation généralisée des contextes de cache dans Drupal 8 permet d’activer par défaut le cache dynamique de page (anciennement « Smart Cache »).
Cache interne de page
Le cache interne de page suppose que toutes les pages servies aux utilisateurs anonymes sont identiques, quelles que soient les variations de contexte. Si vous souhaitez modifier le contenu pour les utilisateurs anonymes selon les contextes, ce cache doit être désactivé, ce qui peut impacter les performances.