Wie richte ich das Caching für meinen benutzerdefinierten Block ein, bei dem der Inhalt abhängig vom aktuellen Knoten angezeigt wird?

19

Ich habe diesen sehr einfachen Block, der nur die ID des aktuellen Knotens anzeigt.

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\ExampleEmptyBlock.
 */

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * @Block(
 *   id = "example_empty",
 *   admin_label = @Translation("Example: empty block")
 * )
 */
class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $node = \Drupal::routeMatch()->getParameter('node');
    $build = array();

    if ($node) {
      $config = \Drupal::config('system.site');

      $build = array(
        '#type' => 'markup',
        '#markup' => '<p>' . $node->id() . '<p>',
        '#cache' => array(
          'tags' => $this->getCacheTags(),
          'contexts' => $this->getCacheContexts(),
        ),
      );
    }

    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

}

Einmal zwischengespeichert, bleibt der Block jedoch derselbe, unabhängig davon, welchen Knoten ich besuche. Wie kann ich das Ergebnis pro Knoten-ID korrekt zwischenspeichern?

Alex
quelle
1
Schauen getCacheTags()Sie sich BlockBase an, Sie müssen nur ein Tag hinzufügen, das Ihren Knoten darstellt (node: {nid}). Tut mir leid, ich habe es jetzt eilig, ich kann es später besser erklären
Vagner

Antworten:

31

Dies ist ein voll funktionsfähiger Code mit Kommentaren.

namespace Drupal\module_name\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * Provides a Node cached block that display node's ID.
 *
 * @Block(
 *   id = "node_cached_block",
 *   admin_label = @Translation("Node Cached")
 * )
 */
class NodeCachedBlock extends BlockBase {
  public function build() {
    $build = array();
    //if node is found from routeMatch create a markup with node ID's.
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      $build['node_id'] = array(
        '#markup' => '<p>' . $node->id() . '<p>',
      );
    }
    return $build;
  }

  public function getCacheTags() {
    //With this when your node change your block will rebuild
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      //if there is node add its cachetag
      return Cache::mergeTags(parent::getCacheTags(), array('node:' . $node->id()));
    } else {
      //Return default tags instead.
      return parent::getCacheTags();
    }
  }

  public function getCacheContexts() {
    //if you depends on \Drupal::routeMatch()
    //you must set context of this block with 'route' context tag.
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }
}

Ich habe es getestet. Es klappt.

Legen Sie den Code einfach in eine Datei mit dem Namen NodeCachedBlock.php in Ihrem Modulordner, ändern Sie den Namespace {Modulname}, leeren Sie den Cache und verwenden Sie ihn.

Vagner
quelle
Der Trick besteht also darin, die #cacheEinstellungen in der Build-Funktion zu entfernen und einfach die öffentlichen Funktionen hinzuzufügen.
Alex
3
Dabei spielt es keine Rolle, wo Sie die Cache-Tags und Kontexte festlegen.
4.
Nun, ich denke, das ist sinnvoller, weil wir einen Block bauen, also muss der Block zwischengespeichert werden. Wenn Sie Ihren Block in Zukunft ändern (dh zusätzliche Render-Elemente einfügen), funktioniert Ihr Block.
Vagner
@ 4k4 url.path schien auch geklappt zu haben. was ist der Unterschied?
Alex
2
@Vagner: Das Einfügen von Cache-Tags / -Kontexten in das Render-Array ist auch keine schlechte Idee, da Sie sie dort haben, wo sich Ihre Daten befinden, die davon abhängen. Und es wird immer explodieren, sodass Sie sich nicht um die darüber liegenden Elemente sorgen müssen. Btw. Ihr Code ist großartig, erklärt die Caching-Probleme sehr gut.
4.
13

Der mit Abstand einfachste Weg, dies zu tun, besteht darin, sich auf das Plugin / Block-Kontext-System zu verlassen.

Siehe meine Antwort für Wie erstelle ich einen Block, der den aktuellen Knoteninhalt abruft?

Sie müssen lediglich eine Knotenkontextdefinition wie folgt in Ihre Blockanmerkung einfügen:

*   context = {
*     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
*   }

Und dann benutze es so: $this->getContextValue('node')

Das Schöne daran ist, dass Drupal sich dann um das Caching für Sie kümmert. Automatisch. Weil bekannt ist, dass der Standardknotenkontext (und nur der Kern) der aktuelle Knoten ist. Und das weiß, woher es kommt, sodass der Cache-Kontext und die Cache-Tags automatisch hinzugefügt werden.

Durch \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()und die entsprechenden getCacheTags()Methoden erweitert sich BlockBase / Ihre Blockklasse davon und erbt diese Methoden.

Berdir
quelle
Sie ersetzen \Drupal::routeMatch()->getParameter('node')mit $this->getContextValue('node')und Sie lösen das gesamte Caching-Problem mit einer Codezeile? Groß!
4.
1
danke soweit! Können Sie ein vollständiges Codebeispiel bereitstellen?
Alex
@Alex: Ich habe deine Frage bearbeitet. Bitte überprüfen und ändern Sie den Code, wenn Sie einen Fehler finden.
4.
@ 4k4 Ich habe es nicht ausprobiert, weil die andere Lösung auch funktioniert
Alex
@ Alex - Vollständiges Codebeispiel: drupal.stackexchange.com/a/205155/15055
leymannx
7

Wenn Sie die Klasse Ihres Block-Plugins ableiten Drupal\Core\Block\BlockBase, haben Sie zwei Methoden, um Cache-Tags und -Kontexte festzulegen.

  • getCacheTags()
  • getCacheContexts()

Beispielsweise implementiert der Buchmodulblock diese Methoden wie folgt.

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['route.book_navigation']);
  }

Der Forum-Modulblock verwendet den folgenden Code.

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    return Cache::mergeTags(parent::getCacheTags(), ['node_list']);
  }

In Ihrem Fall würde ich den folgenden Code verwenden.

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

Sie können auch die folgende Methode verwenden, um den Block überhaupt nicht zwischenspeicherbar zu machen (auch wenn ich ihn vermeiden würde). Es könnte vielleicht in anderen Fällen nützlich sein.

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return 0;
  }

Denken Sie daran, use Drupal\Core\Cache\Cache;oben in die Datei einzufügen, wenn Sie die CacheKlasse verwenden möchten.

kiamlaluno
quelle
danke, aber auf / node / 2 gibt der Block immer noch 1 aus, wenn ich zuerst node / 1 besucht habe, nachdem ich meinen Cache
Alex
2
Wenn Sie ein aktiviertes Modul bearbeiten, müssen Sie es zuerst deinstallieren, bevor Sie es bearbeiten können. Das Leeren des Caches reicht nicht aus.
Kiamlaluno
okay, aber das Hinzufügen von maxAge 0 funktioniert seltsamerweise!
Alex
Verwendet Ihre Blockklasse die BlockBaseKlasse auch als übergeordnete Klasse?
Kiamlaluno
Ja, es nutzt es
Alex
3

Wenn Sie ein Render-Array erstellen, hängen Sie immer die richtigen Metadaten an:

use Drupal\Core\Cache\Cache;

$build['node_id'] = [
  '#markup' => '<p>' . $node->id() . '<p>',
  '#cache' => [
    'tags' => $node->getCacheTags(),
    // add a context if the node is dependent on the route match
    'contexts' => ['route'],
  ],
];

Dies ist nicht blockspezifisch und die Cache-Abhängigkeitsmethoden getCacheTags (), getCacheContext () und getCacheMaxAge () der Block-Plugins sind kein Ersatz. Sie sollten nur für zusätzliche Cache-Metadaten verwendet werden, die nicht über das Render-Array bereitgestellt werden können.

Siehe die Dokumentation:

"Es ist äußerst wichtig, dass Sie die Render-API über die Cachefähigkeit eines Render-Arrays informieren."

https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays

Sehen Sie dieses Beispiel , wie Drupal Array einen Render erwartet , dass die notwendigen Cache - Metadaten zu liefern , wenn das Caching durch Auto-placeholdering Optimierung und lazy-Aufbau Problem Einstellung benutzerspezifischen Cache - Tags auf benutzerdefinierten Block mit Benutzerkontext

4k4
quelle
Ich glaube nicht, dass der Cache des Block-Objekts gesetzt werden kann. '#markup' ist nur ein Renderelement-Objekt und es gibt keinen Grund, den Cache-Kontext oder das Tag festzulegen. Das Block-Objekt, das neu erstellt werden muss, wenn der Cache ungültig ist.
Vagner
#markupkann wie jedes andere Renderelement zwischengespeichert werden. In diesem Fall ist es nicht das Markup, sondern der Block, der zwischengespeichert wird und hier liegt das Problem. Sie können es nicht mit Cache-Tags lösen, da diese nur ungültig werden, wenn der Knoten in der Datenbank geändert wird.
4.
@Vagner Sie können den Cache eines Block-Objekts festlegen. Die BlockBaseKlasse hat sogar die notwendigen Methoden.
Kiamlaluno
1
Bei mir return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];funktioniert das super prima per URL-Caching.
Leymannx
1
Ja, @leymannx, so einfach ist das. Dieser Thread scheint das Problem zu überdenken.
4. 4.
0

Das Problem hierbei ist, dass die Cache-Kontexte in der Build-Funktion nicht an der richtigen Stelle deklariert werden:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');
   $build = array();

   if ($node) {
    $config = \Drupal::config('system.site');

    $build = array(
    '#type' => 'markup',
    '#markup' => '<p>' . $node->id() . '<p>',
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );
 }
 return $build;
 }
}

Wenn Sie diesen Block auf einem Nicht-Knoten aufrufen, gibt die Erstellungsfunktion ein leeres Array zurück, sodass für diesen Block kein Cache-Kontext vorhanden ist und dieses Verhalten von Drupal zwischengespeichert wird: Die Anzeige dieses Blocks wird nicht ordnungsgemäß ungültig gemacht oder gerendert.

Die Lösung besteht darin, den $ build jedes Mal mit den Cache-Kontexten zu initialisieren:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');

    $build = array(
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );

   if ($node) {
    $config = \Drupal::config('system.site');

    $build['#markup'] = '<p>' . $node->id() . '<p>';
    $build['#type'] = 'markup';
    }
 return $build;
 }
}
List
quelle
0

Mir ist klar, dass ich zu spät zu dieser Unterhaltung komme, aber der folgende Code hat bei mir funktioniert:

class ExampleBlock extends BlockBase
{

  public function build()
  {
    $lcContent = '';

    $loNode = \Drupal::routeMatch()->getParameter('node');

    if (!$loNode)
    {
      return (array(
        '#type' => 'markup',
        '#cache' => array('max-age' => 0),
        '#markup' => $lcContent,
      ));
    }

    $lcContent .= "<div id='example_block' style='overflow: hidden; clear: both;'>\n";
    $lcContent .= $loNode->id();
    $lcContent .= "</div>\n";

    return (array(
      '#type' => 'markup',
      '#cache' => array('max-age' => 0),
      '#markup' => $lcContent,
    ));
  }
}
Eddie Fann
quelle
Besser spät als nie :)
Alex
0

Haben Sie versucht, hook_block_view_BASE_BLOCK_ID_alter zu implementieren?

function hook_block_view_BASE_BLOCK_ID_alter (Array & $ build, \ Drupal \ Core \ Block \ BlockPluginInterface $ block) {$ build ['# cache'] ['max-age'] = 0; }

Bolleddula Sambasiva Rao
quelle