CacheableDependencyInterface
Um die Arbeit mit Cache-Metadaten (Cache-Tags, Cache-Kontexte und Max-Age) zu erleichtern, gibt es in Drupal 8 das Interface CacheableDependencyInterface
.
Warum?
Stellen Sie sich vor, Sie müssten manuell die Cache-Tags jedes einzelnen Objekts und Konfigurationsobjekts, das Sie in einem Render-Array (oder anderen Berechnungen) verwenden, angeben. Und auf einer mehrsprachigen Website müssten Sie auch noch die nötigen Cache-Kontexte manuell hinzufügen (entweder für übersetzte Entitäten oder zur Überschreibung der Sprache eines Konfigurationsobjekts).
Und nicht nur Entitäten und Konfigurationen, sondern auch Access Results, Block-Plugins, Menülinks, Kontext-Plugins, Bedingungs-Plugins usw., da all diese letztlich zum Rendering (einer speziellen Art von Berechnung) führen, das wir gerne cachen möchten.
In den frühen Tagen der Drupal 8-Entwicklung war das noch so. Es ist klar, dass das unzuverlässig war und viele Fehler verursachte.
Deshalb wurde das Interface CacheableDependencyInterface
eingeführt. Wie der Name sagt: Objekte, die dieses Interface implementieren, können automatisch als cachebare Abhängigkeiten fungieren.
Zum Beispiel beim Erstellen eines Render-Arrays für <p>Hi, %user, welcome to %site!</p> verlassen Sie sich sowohl auf die User-Entität des aktuellen Benutzers als auch auf die Konfiguration system.site
. Wenn dieses Render-Array gecacht wird, beinhaltet es sowohl die User-Entität als auch das Konfigurationsobjekt als cachebare Abhängigkeiten.
CacheableDependencyInterface
kann von jedem Wertobjekt (also einem Objekt, das eine logische Dateneinheit repräsentiert) implementiert werden. Wenn Sie sich seine API-Dokumentation ansehen, sehen Sie, dass es von vielen wichtigen Objekten im Drupal 8-Core implementiert wird. Tatsächlich kann man mit Sicherheit sagen, dass es von den meisten Objekten implementiert wird, mit denen Sie im Drupal-8-Code interagieren!
Es gibt zwei entgegengesetzte Fälle, die recht häufig vorkommen und für die Drupal praktische Traits bietet: den Fall eines unveränderlichen Objekts, das somit permanent cachebar ist (UnchangingCacheableDependencyTrait, der immer max-age === permanent
zurĂĽckgibt), und den Fall eines Objekts, das immer dynamisch berechnet wird und deshalb nie gecacht wird (UncacheableDependencyTrait, der immer max-age === 0
zurĂĽckgibt).
RefinableCacheableDependencyInterface
Aber CacheableDependencyInterface
kann nur mit den „inhärenten“, „kanonischen“ Cache-Metadaten eines Objekts arbeiten. Manchmal gibt es jedoch mehrere Varianten eines Objekts.
Die offensichtlichsten Beispiele sind Entitätsübersetzungen (dasselbe Entity mit derselben ID, aber in einer anderen Übersetzung) und Konfigurationsübersetzungen (dasselbe Konfigurationsobjekt mit dem gleichen Namen, aber mit einer Sprachüberschreibung).
In beiden Fällen bleiben die Cache-Metadaten des ursprünglichen (nicht übersetzten) Objekts gültig (z. B. node:5
als Cache-Tag). Aber – im Fall der Entität – ist der Cache-Kontext der Inhalts-Sprache notwendig ('languages:' . LanguageInterface::TYPE_CONTENT, siehe Cache-Kontexte), um anzuzeigen, dass diese Entität eine Variante der ursprünglichen Entität ist, die je nach vereinbartem Sprach-Cache-Kontext variiert. Ebenso ist im Fall eines Konfigurationsobjekts der Cache-Kontext der Interface-Sprache ('languages:' . LanguageInterface::TYPE_INTERFACE) notwendig, um anzuzeigen, dass dieses Konfigurationsobjekt eine Variante des ursprünglichen Konfigurationsobjekts ist, die je nach vereinbartem Interface-Sprach-Cache-Kontext variiert.
Ein Beispiel, das über Übersetzungen hinausgeht, ist ein Modul „Pirate Day“, das eine Konfigurationsüberschreibung enthält, die nur am Piratentag angewendet wird und der Konfiguration „yar“, „har“ und zufällige Papageien hinzufügt; daher haben Konfigurationsobjekte dann den Cache-Kontext pirate_day
.
In all diesen Beispielen müssen wir die Cache-Metadaten unserer Objekte verfeinern, um die geladene Variante anzugeben. Aus diesem Grund wurde das Interface RefinableCacheableDependencyInterface eingeführt, das genau das ermöglicht: Es erlaubt, Cache-Tags, Kontexte und max-age hinzuzufügen oder zu aktualisieren.
Zur Vereinfachung der Implementierung dieses Interfaces gibt es zudem den praktischen Trait RefinableCacheableDependencyTrait.
Über Entitäten und Konfigurationsobjekte
Alle Entitäten in Drupal 8 (Core, Contrib & Custom) implementieren das EntityInterface, das sowohl CacheableDependencyInterface als auch RefinableCacheableDependencyInterface erweitert. Außerdem erweitern alle Core-Entitäten die abstrakte Basisklasse Entity, und es wird empfohlen, dass Contrib- und Custom-Entitäten dies ebenfalls tun. Das bedeutet, dass jede einzelne Entität, mit der Sie in Drupal 8 interagieren, automatisch konsistente Cache-Tags (<entity type>:<entity ID>, z. B. node:5 und user:3) und Cache-Kontexte für Übersetzungen besitzt.
Alle Konfigurationsobjekte im Drupal 8-Core erweitern die abstrakte Basisklasse ConfigBase, die sowohl CacheableDependencyInterface
als auch RefinableCacheableDependencyInterface
implementiert. Das bedeutet, dass jedes einzelne Konfigurationsobjekt, mit dem Sie in Drupal 8 interagieren, automatisch konsistente Cache-Tags (in der Form config:<Konfigurationsname>
, z. B. config:system.performance) und Cache-Kontexte fĂĽr KonfigurationsĂĽberschreibungen besitzt (deren Ăśbersetzungen das einzige Beispiel im Core sind).
Schließlich haben alle Entitäten und Konfigurationsobjekte in Drupal 8 automatisch die Cache-Kontexte für Inhalts- bzw. Interface-Sprachen dank EntityManager::getTranslationFromContext() und LanguageConfigFactoryOverride::getCacheableMetadata($name).
Verwendung von Objekten, die cachebare Abhängigkeiten sind
Rendering ist das häufigste Beispiel für eine Abhängigkeit von einem Objekt, das eine cachebare Abhängigkeit ist. Zur Vereinfachung gibt es RendererInterface::addCacheableDependency($build, $dependency) – wobei $build das Render-Array ist, das von $dependency abhängt; die Cache-Metadaten des cachebaren Objekts werden automatisch vom Render-Array „aufgesogen“. Das bedeutet, dass das Render-Array invalidiert wird, wenn ein Cache-Tag des Objekts invalidiert wird und so eine andere Version gecacht wird, wenn eine andere Übersetzung verwendet wird (d. h. der Cache-Kontext der Inhalts-Sprache wird auf eine andere Sprache abgebildet), und dass es automatisch abläuft, wenn die Abhängigkeit eine maximale Lebensdauer hat, die nicht permanent ist.
Siehe Cachable Render Arrays – ein konkretes Beispiel für eine vollständige Umsetzung.
Ein weiteres gutes Beispiel sind Zugriffsprüfungen, die AccessResult-Objekte zurückgeben, die ebenfalls die Methode AccessResult::addCacheableDependency($dependency) besitzen. Beachten Sie, dass wir hier nur den Parameter $dependency haben, weil wir die Cache-Metadaten der übergebenen Abhängigkeiten auf dem AccessResult-Objekt selbst speichern können. (Rendering mit Render-Arrays ist hier die Ausnahme.)