Was ist der richtige Weg, um Cache-Kontexte für benutzerdefinierte Blöcke festzulegen?

13

Ich bin auf ein Problem gestoßen, bei dem ein Block, der pro Seite eindeutig sein sollte, nicht für abgemeldete Benutzer geeignet ist. Das Problem ist ein benutzerdefiniertes Block-Plugin, das ich auf einer Ansichtssuchseite habe und das benutzerdefinierte Filter enthält (ähnlich wie ein benutzerdefinierter Ersatz für exponierte Filter. Der Block wird durch / admin / structure / block platziert).

Basierend auf dem, was ich über Drupal 8 gelernt habe, habe ich die Cache-Kontexte zu meinem Build-Array hinzugefügt:

  public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      'search_form' => $search_form,
      '#cache' => ['contexts' => ['url.path', 'url.query_args']]
    ];

  }

Dies muss jedoch falsch sein, da der Block beim Abmelden bei der ersten Ansicht zwischengespeichert wird und bei Änderung der URL keine neue Version des Blocks angezeigt wird.

Ich dachte, es könnte die Ansichtsseite sein, die das Problem verursacht hat, aber selbst als ich das Caching auf der Ansichtsseite deaktivierte, blieb das Problem bestehen.

Ich konnte das Problem auf verschiedene Arten beheben, beispielsweise mithilfe eines preprocess_block-Hooks:

function mymodule_preprocess_block__mycustomsearchblock(&$variables) {
  $variables['#cache']['contexts'][] = 'url.path';
  $variables['#cache']['contexts'][] = 'url.query_args';
}

Aber es störte mich, dass ich die Cache-Kontexte nicht einfach in das Build-Array meines Blocks einfügen konnte.

Da mein Block BlockBase erweitert, habe ich beschlossen, die Methode getCacheContexts () auszuprobieren, insbesondere weil ich gesehen habe, dass einige Module im Kern dies auf diese Weise tun.

  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['url.path', 'url.query_args']);
  }

Dies hat auch das Problem behoben, aber interessanterweise werden diese, wenn ich die Variablen in der Vorverarbeitungsblockfunktion ausgeben, nicht in $ variables ['# cache'] ['context'] angezeigt, sondern in $ variables ['Elementen '] [' # cache '] [' context ']

array:5 [▼
  0 => "languages:language_interface"
  1 => "theme"
  2 => "url.path"
  3 => "url.query_args"
  4 => "user.permissions"
]

Ich versuche herauszufinden, wie dies funktioniert und warum es mit der Build-Funktion nicht funktioniert hat.

Wenn Sie sich /core/modules/block/src/BlockViewBuilder.php in der Funktion viewMultiple () ansehen, sieht es so aus, als würden die Cache-Tags aus der Entität und dem Plugin abgerufen:

'contexts' => Cache::mergeContexts(
  $entity->getCacheContexts(),
  $plugin->getCacheContexts()
),

Das erklärt, warum das Hinzufügen einer getCacheContexts () -Methode zu meinem Block-Plugin die Kontexte zu meinem Block hinzufügt. Wenn man sich die preRender-Methode in derselben Klasse ansieht, sieht es so aus, als würde das Cache-Array in der Blockerstellungsfunktion nicht verwendet, was mich verwirrt, da das Hinzufügen von Caching in Drupal 8 anscheinend darin besteht, einen #cache hinzuzufügen Element zum Rendern von Elementen.

Meine Frage ist also:

1) Werden Cache-Kontexte, die direkt zum Array in einem Block-Plugin hinzugefügt wurden, ignoriert?

2) Wenn ja, gibt es einen Weg, dies zu umgehen, müssen wir es einem untergeordneten Element des Build-Arrays hinzufügen?

3) Wenn der direkt hinzugefügte Kontext ignoriert wird, ist das Hinzufügen von getCacheContexts () der richtige Weg, um Block-Plugins in benutzerdefinierten Modulen zu verwenden?

oknate
quelle
1
1) Nein, Ihr Blockinhalt ist tatsächlich eine Stufe tiefer und sollte später zusammengeführt werden. 2) Nicht erforderlich, da 1, 3) die Implementierung von getCacheContexts () einfacher / sauberer sein kann, aber nicht erforderlich sein sollte. Sie erwähnen ausdrücklich anonyme Benutzer. Sind Sie sicher, dass dies auch keine normalen authentifizierten Benutzer betrifft? Verschwindet das Problem, wenn Sie dynamic_page_cache deaktivieren? Es muss etwas Seltsames passieren, wenn es nur einzelne Benutzer betrifft, da der interne Seiten-Cache ohnehin immer je nach URL / Abfrage-Argumenten variiert.
Berdir
1
Durch Deaktivieren des dynamischen Seitencaches wird das Problem nicht behoben.
oknate
1
Hm, es könnte ein Problem mit der Tatsache sein, dass Ihr Element der obersten Ebene nichts außer #cache enthält. Haben Sie versucht, einfach #cache in Ihrem Formular festzulegen? Es ist die Form, die von diesen variieren muss, und da Cache-Tags sprudeln, sollte das einfach funktionieren. Und wenn Sie Ihr Formular jemals an einem anderen Block oder an einem anderen Ort verwenden, sollte es auch nur dort funktionieren.
Berdir
1
Ich habe SyndicateBlock schnell gehackt und diese build () -Methode verwendet: gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Wenn Sie festlegen, dass dies auf meiner Website einwandfrei funktioniert, werden die Cache-Kontexte in der Tabelle cache_render angezeigt und der richtige Anforderungs-URI wird angezeigt. Kannst du das auch versuchen? Mir scheint, dass auf Ihrer Website etwas sehr Seltsames vor sich geht
Berdir,
2
Verwenden Sie die Standardblockvorlage oder eine benutzerdefinierte? Siehe drupal.stackexchange.com/questions/217884/…
4k4

Antworten:

9

In den meisten Fällen legen Sie den Cache-Kontext einfach direkt auf dem Render-Array fest, das Sie in Ihrer build () -Methode zurückgeben.

Mit Hilfe von @Berdir und @ 4k4 habe ich endlich herausgefunden, was mein Problem war. Wenn Sie eine benutzerdefinierte Vorlage wie block - myblock.html.twig verwenden und die Variablen einzeln ausgeben, z. B. {{content.foo}}, anstatt alle gleichzeitig wie {{content}}, werden sie ignoriert Ihre Cache-Kontexte werden beim Abmelden direkt an Ihr Blockbuild-Array übergeben. Siehe Was ist der richtige Weg, um Cache-Kontexte für benutzerdefinierte Blöcke festzulegen?

Um die ursprüngliche Frage zu beantworten:

1) Cache-Kontexte, die direkt an ein benutzerdefiniertes Block-Plugin übergeben werden, werden manchmal ignoriert. Sie können dies testen, indem Sie den SyndicateBlock ändern und dann eine benutzerdefinierte Vorlage in Ihrem Themenblock erstellen - syndicate.html.php, in der Sie die Variablen einzeln wie folgt ausgeben:

{% block content %}
  {{ content.foo }}
{% endblock %}

Wenn Sie die URL-Argumente ändern, wird der Block den Cache-Kontext nicht berücksichtigen.

Wenn Sie nun den gesamten Inhalt als Stück ausgeben, funktioniert es:

{% block content %}
  {{ content }}
{% endblock %}

Jetzt wird der Cache-Kontext berücksichtigt, und der Block ist pro Seite eindeutig.

2) Um dies zu umgehen, können Sie zunächst nur das, was sich in Ihrem Block befindet, in einer eigenen Vorlage ausgeben.

 public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      '#theme' => 'mycustomtemplate',
      '#search_form' => $search_form,
      '#cache' => ['contexts' => ['url.path', 'url.query_args']]
    ];

  }

Dadurch werden die esoterischen Caching-Ausnahmen des Blockmoduls umgangen, und Ihr Formular ist jetzt beim Abmelden pro Seite eindeutig.

3) Sollten Sie eine eigene Designvorlage erstellen, um dies zu beheben, oder einfach eine Methode für getCacheContexts () in Ihrem benutzerdefinierten Block-Plugin hinzufügen? Es ist besser, eine neue Designvorlage zu erstellen, als eine getCacheContexts () -Methode hinzuzufügen, die die natürliche Reihenfolge des Aufsprudelns von Cache-Kontexten überschreibt und möglicherweise Metadaten tiefer in Ihrem Build-Array aufbricht.

oknate
quelle
Dies ist eine sehr gute Zusammenfassung des Problems. Aber ich denke, die Schlussfolgerung in 3) ist problematisch, weil Sie nicht nur brechen, dass Ihre eigenen Cache-Metadaten sprudeln können, sondern auch die, die tiefer im Render-Array liegen und die Sie möglicherweise nicht kennen.
4k4
Sie würden also vorschlagen, eine neue Themenvorlage zu erstellen? Um ein klares Sprudeln zu bewahren?
oknate
Ja, verwenden Sie die Blockvorlage nur, um Dinge nach außen hinzuzufügen. Erstellen Sie das Innere des Blocks in build (). Verwenden Sie hierfür benutzerdefinierte Vorlagen oder Renderelemente wie eine Tabelle oder eine Kernvorlage wie Links.
4k4
OK, ich werde die Antwort aktualisieren.
oknate
Ugh, ich wusste nicht, dass Twigs Drill-In die zwischenspeicherbaren Metadaten ignorieren würde. Dies kann bedeuten, dass wir am Ende unsere eigene benutzerdefinierte Methode verwenden müssen, um einen Drill-In durchzuführen (wodurch die Zweigerweiterung unbrauchbar wird), damit wir Metadaten beibehalten, während wir in niedrigere Ebenen gehen. Guter Fund!
LionsAd
4

Für alle anderen, die dies finden ...

Der Grund, warum das Rendern content(oder content|without()) funktioniert, ist, dass das Render-Array ein Element content['#cache']enthält, das alle zwischenspeicherbaren Metadaten für den Inhalt enthält.

Wenn Sie nicht zulassen, dass dies im Zweig gerendert wird, contentoder {{'#cache': content['#cache']|render }}die Seite nicht weiß, dass sie zwischenspeicherbare Metadaten enthält (z. B. sprudelt sie nie).

Klingt so, als ob Sie keinen benutzerdefinierten Zweig machen. Wenn Sie etwas wie Lack verwenden, kann dies auch ein Schuldiger für anonyme Benutzer sein.

Köper
quelle
3

Ich bin auch auf dieses Problem gestoßen. Die Problemumgehung bestand darin, eine neue Variable block_content zu erstellen, die auf einer gefilterten Version der Hauptinhaltsvariablen basiert, mit Ausnahme aller benutzerdefinierten Felder, die ich manuell rendern möchte:

{% set block_content = content|without('field_mycustomfield', 'field_mycustomfield2') %}

Anstatt die Variable "content.body" direkt später zu rendern, rufe ich auf:

{{ block_content }}

Wenn Sie jedes Feld einzeln rendern möchten, können Sie es einfach weiter zum Filter "ohne" hinzufügen, damit beim Rendern von block_content nur das Caching korrigiert wird.

Ryan Barkley
quelle
0

Die einfachere Methode, um dies zu erreichen, besteht darin, die getCacheContexts()Methode zu deklarieren und zu definieren


  public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      'search_form' => $search_form
    ];

  }

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    // If you need to redefine the Max Age for that block
    return 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return ['url.path', 'url.query_args'];
  }

Schauen Sie sich die CacheableDependency- Dokumentation an, sie sollte alles enthalten, was Sie brauchen;)

Ben Cassinat
quelle
Dies funktioniert nicht mehr so, wie Blöcke jetzt gerendert werden, siehe drupal.stackexchange.com/questions/288881/…
4k4