web-dev-qa-db-fra.com

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

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?

22
Alex

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.

36
Vagner

La manière 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 alors 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 de \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts() et des méthodes getCacheTags() correspondantes, BlockBase/votre classe de bloc s'étend de cela et hérite de ces méthodes.

14
Berdir

Si vous dérivez la classe de votre plugin de bloc de 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 classe Cache.

8
kiamlaluno

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 l'auto-espace réservé et la construction paresseuse Problème lors de la définition de balises de cache spécifiques à l'utilisateur sur un bloc personnalisé avec l'utilisateur) contexte

5
4k4

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

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

Cela m'a pris un certain temps pour trouver la mise en cache des blocs dans drupal 8, alors j'ai fait ce tutoriel vidéo expliquant la mise en cache des blocs (16 min.) .

1
Stef Van Looveren

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 non noeud, la fonction de construction retourne un tableau vide, donc il n'y a 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;
 }
}
1
Guile

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,
    ));
  }
}
1
Eddie Fann