Comment configurer correctement la mise en cache pour mon bloc personnalisé affichant le contenu en fonction du nœud actuel?

19

J'ai ce bloc très basique qui montre juste l'ID du nœud actuel.

<?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']);
  }

}

Mais une fois mis en cache, le bloc reste le même, quel que soit le nœud que je visite. Comment mettre correctement en cache le résultat par ID de nœud?

Alex
la source
1
Regardez getCacheTags()depuis BlockBase, il vous suffit d'ajouter une balise qui représente votre nœud (nœud: {nid}). Désolé, je suis pressé maintenant, je peux mieux expliquer plus tard,
Vagner

Réponses:

31

Il s'agit d'un code de travail complet avec des commentaires.

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'));
  }
}

Je l'ai testé; Ça marche.

Placez simplement le code dans un fichier nommé NodeCachedBlock.php dans le dossier de votre module, changez son espace de nom {module_name}, videz le cache et utilisez-le.

Vagner
la source
donc l'astuce est de supprimer les #cacheparamètres de la fonction de construction et d'ajouter simplement les fonctions publiques?
Alex
3
Peu importe où vous définissez les balises de cache et les contextes.
4k4
Eh bien, je pense que cela a plus de sens, car nous construisons un bloc, donc le bloc doit être mis en cache. Si vous changez votre bloc à l'avenir (c'est-à-dire que vous mettez des éléments de rendu supplémentaires), votre bloc fonctionnera.
Vagner
@ 4k4 url.path semblait également avoir fonctionné. quelle est la différence?
Alex
2
@Vagner: Placer des balises / contextes de cache dans le tableau de rendu n'est pas non plus une mauvaise idée, car vous l'avez là où se trouvent vos données, qui en dépendent. Et il bouillonnera toujours, vous n'avez donc pas à vous soucier des éléments ci-dessus. Btw. votre code est génial, explique très bien les problèmes de mise en cache.
4k4
13

La façon de loin la plus simple de le faire est de s'appuyer sur le système de contexte plugin / bloc.

Voir ma réponse pour Comment créer un bloc qui extrait le contenu du nœud actuel?

Il vous suffit de mettre une définition de contexte de nœud dans votre annotation de bloc comme ceci:

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

Et puis utilisez-le comme ceci: $this->getContextValue('node')

La bonne chose à ce sujet est que Drupal se chargera ensuite de la mise en cache pour vous. Automatiquement. Parce qu'il sait que le contexte de nœud par défaut (et pour autant que le cœur ne va que) est le nœud actuel. Et qui sait d'où il vient, le contexte de cache et les balises de cache sont ajoutés automatiquement.

Par le biais \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()et les getCacheTags()méthodes correspondantes , BlockBase / votre classe de bloc s'étend de cela et hérite de ces méthodes.

Berdir
la source
Vous remplacez \Drupal::routeMatch()->getParameter('node')par $this->getContextValue('node')et vous résolvez tout le problème de mise en cache avec une seule ligne de code? Génial!
4k4
1
merci jusqu'ici! pourriez-vous fournir un exemple de code complet?
Alex
@Alex: J'ai édité votre question. Veuillez vérifier et modifier le code si vous trouvez une erreur.
4k4
@ 4k4 Je ne l'ai pas essayé parce que l'autre solution fonctionne aussi
Alex
@Alex - Exemple de code complet: drupal.stackexchange.com/a/205155/15055
leymannx
7

Si vous dérivez la classe de votre plugin de bloc Drupal\Core\Block\BlockBase, vous aurez deux méthodes pour définir les balises de cache et les contextes.

  • getCacheTags()
  • getCacheContexts()

Par exemple, le bloc de module Book implémente ces méthodes comme suit.

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

Le bloc de module Forum utilise le code suivant.

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

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

Dans votre cas, j'utiliserais le code suivant.

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

Vous pouvez également utiliser la méthode suivante, pour rendre le bloc impossible à mettre en cache (même si je l'évite). Cela pourrait être utile dans d'autres cas, peut-être.

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

N'oubliez pas d'ajouter use Drupal\Core\Cache\Cache;en haut du fichier, si vous allez utiliser la Cacheclasse.

kiamlaluno
la source
merci, mais sur / node / 2, le bloc affiche toujours 1 lorsque j'ai visité node / 1 en premier lieu, après avoir vidé mon cache
Alex
2
Si vous modifiez un module qui est activé, vous devez d'abord le désinstaller, avant de le modifier. Vider le cache ne suffit pas.
kiamlaluno
d'accord, mais l'ajout de la maxAge 0 fonctionne, étrangement!
Alex
De plus, votre classe de blocs utilise-t-elle la BlockBaseclasse comme classe parente?
kiamlaluno
oui il l'utilise
Alex
3

Lorsque vous créez un tableau de rendu, attachez toujours les métadonnées correctes:

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'],
  ],
];

Ce n'est pas spécifique au bloc et les méthodes de dépendance du cache des plugins de bloc getCacheTags (), getCacheContext () et getCacheMaxAge () ne sont pas un remplacement. Ils ne doivent être utilisés que pour des métadonnées de cache supplémentaires, qui ne peuvent pas être fournies via le tableau de rendu.

Consultez la documentation:

"Il est de la plus haute importance que vous informiez l'API Render de la possibilité de mise en cache d'un tableau de rendu."

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

Voir cet exemple comment Drupal s'attend à ce qu'un tableau de rendu fournisse les métadonnées de cache nécessaires lors de l'optimisation de la mise en cache via la mise en place automatique et la construction paresseuse Problème de définition de balises de cache spécifiques à l'utilisateur sur un bloc personnalisé avec un contexte utilisateur

4k4
la source
Je ne pense pas que cela puisse définir le cache de l'objet Block. '#markup' est juste un objet d'élément de rendu et il n'y a aucune raison de définir le contexte ou la balise de cache. L'objet bloc qui doit être reconstruit lorsque le cache est invalidé.
Vagner
#markuppeut être mis en cache comme n'importe quel autre élément de rendu. Dans ce cas, ce n'est pas le balisage, mais le bloc, qui est mis en cache et voici le problème. Vous ne pouvez pas le résoudre avec des balises de cache, car elles ne sont invalidées que si le nœud est modifié dans la base de données.
4k4
@Vagner Vous pouvez définir le cache d'un objet Block; La BlockBaseclasse a même les méthodes nécessaires.
kiamlaluno
1
Pour moi, return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];fonctionne très bien pour la mise en cache par URL.
leymannx
1
Oui, @leymannx, c'est aussi simple que cela. Ces discussions semblent sur-penser le problème.
4k4
0

Le problème ici est que les contextes de cache ne sont pas déclarés au bon endroit dans la fonction de génération:

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;
 }
}

Si vous appelez ce bloc sur un noeud non, la fonction de construction retourne un tableau vide, il n'y a donc pas de contexte de cache pour ce bloc et ce comportement sera mis en cache par drupal: l'affichage de ce bloc ne sera pas correctement invalidé ou restitué.

La solution consiste simplement à initialiser la construction de $ avec les contextes de cache à chaque fois:

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;
 }
}
Ruse
la source
0

Je me rends compte que je suis en retard à cette conversation, mais le code ci-dessous a fonctionné pour moi:

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
la source
mieux vaut tard que jamais :)
Alex
0

Avez-vous essayé d'implémenter hook_block_view_BASE_BLOCK_ID_alter?

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

Bolleddula Sambasiva Rao
la source