Come posso impostare correttamente la memorizzazione nella cache per il mio blocco personalizzato che mostra il contenuto in base al nodo corrente?


19

Ho questo blocco molto semplice che mostra solo l'ID del nodo corrente.

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

}

Ma una volta memorizzato nella cache, il blocco rimane lo stesso, indipendentemente dal nodo che visito. Come posso memorizzare correttamente nella cache il risultato per ID nodo?


1
Guarda getCacheTags()da BlockBase, devi solo aggiungere un tag che rappresenti il ​​tuo nodo (nodo: {nid}). Mi dispiace, adesso ho fretta, posso spiegare meglio dopo,
Vagner,

Risposte:


31

Questo è il codice di lavoro completo con commenti.

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

L'ho provato; Funziona.

Basta inserire il codice in un file chiamato NodeCachedBlock.php nella cartella del modulo, cambiare il suo spazio dei nomi {nome_modulo}, cancellare la cache e usarlo.


quindi il trucco è rimuovere le #cacheimpostazioni nella funzione build e aggiungere semplicemente le funzioni pubbliche?
Alex,

3
Non importa dove si impostano i tag e i contesti della cache.
4k4,

Bene, penso che abbia più senso, perché stiamo costruendo un blocco, quindi il blocco deve essere memorizzato nella cache. Se in futuro si modifica il blocco (ad esempio, si aggiungono alcuni elementi di rendering extra), il blocco funzionerà.
Vagner,

@ 4k4 url.path sembrava aver funzionato troppo. qual è la differenza?
Alex,

2
@Vagner: Mettere tag / contesti della cache nell'array di rendering non è una cattiva idea, perché li hai dove sono i tuoi dati, che dipendono da essi. E farà sempre capolino, quindi non devi preoccuparti degli elementi che sono sopra. Btw. il tuo codice è ottimo, spiega molto bene i problemi di memorizzazione nella cache.
4k4,

13

Il modo più semplice per farlo è fare affidamento sul sistema di contesto plugin / blocco.

Vedi la mia risposta per Come si crea un blocco che estrae il contenuto del nodo corrente?

Devi solo inserire una definizione del contesto del nodo nell'annotazione del blocco in questo modo:

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

E poi usalo in questo modo: $this->getContextValue('node')

La cosa bella di questo è che Drupal si prenderà cura della cache per te. Automaticamente. Perché sa che il contesto del nodo predefinito (e per quanto riguarda solo il core) è il nodo corrente. E questo sa da dove proviene, quindi il contesto della cache e i tag della cache vengono aggiunti automaticamente.

Attraverso \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()e i getCacheTags()metodi corrispondenti , BlockBase / la tua classe di blocchi si estende da quello ed eredita quei metodi.


Sostituisci \Drupal::routeMatch()->getParameter('node')con $this->getContextValue('node')e risolvi l'intero problema di memorizzazione nella cache con una riga di codice? Grande!
4k4,

1
grazie finora! potresti fornire un esempio di codice completo?
Alex,

@Alex: ho modificato la tua domanda. Controlla e modifica il codice se trovi qualche errore.
4k4,

@ 4k4 Non l'ho provato perché anche l'altra soluzione funziona
Alex,

@Alex - Esempio di codice completo: drupal.stackexchange.com/a/205155/15055
leymannx,

7

Se derivate la classe del plug-in di blocco da Drupal\Core\Block\BlockBase, avrete due metodi per impostare tag e contesti della cache.

  • getCacheTags()
  • getCacheContexts()

Ad esempio, il blocco del modulo Libro implementa questi metodi come segue.

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

Il blocco del modulo Forum utilizza il seguente codice.

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

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

Nel tuo caso, userei il seguente codice.

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

Potresti anche usare il seguente metodo, per rendere il blocco invariabile (anche se lo eviterei). Potrebbe essere utile in altri casi, forse.

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

Ricorda di aggiungere use Drupal\Core\Cache\Cache;nella parte superiore del file, se intendi utilizzare la Cacheclasse.


grazie, ma su / node / 2 il blocco emette ancora 1 quando ho visitato il nodo / 1 in primo luogo, dopo aver cancellato la cache
Alex,

2
Se stai modificando un modulo abilitato, devi prima disinstallarlo, prima di modificarlo. Svuotare la cache non è sufficiente.
kiamlaluno

ok, ma l'aggiunta di maxAge 0 funziona, stranamente!
Alex,

Inoltre, la tua classe a blocchi usa la BlockBaseclasse come classe genitore?
kiamlaluno

sì, lo usa
Alex,

3

Quando si crea un array di rendering, collegare sempre i metadati corretti:

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

Questo non è specifico del blocco e i metodi di dipendenza cache dei plug-in di blocco getCacheTags (), getCacheContext () e getCacheMaxAge () non sono un rimpiazzo. Dovrebbero essere usati solo per metadati di cache aggiuntivi, che non possono essere consegnati attraverso l'array di rendering.

Vedi la documentazione:

"È della massima importanza informare l'API di rendering della cache di un array di rendering."

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

Vedi questo esempio in che modo Drupal prevede che un array di rendering fornisca i metadati della cache necessari durante l'ottimizzazione della memorizzazione nella cache tramite auto-placeholdering e lazy-building Problema durante l'impostazione di tag cache specifici dell'utente su blocco personalizzato con contesto utente


Non penso che possa impostare la cache dell'oggetto Block. '#markup' è solo un oggetto Render Element e non c'è motivo di impostare il contesto o il tag della cache. L'oggetto blocco che deve essere ricostruito quando la cache non è valida.
Vagner,

#markuppuò essere memorizzato nella cache come qualsiasi altro elemento di rendering. In questo caso non è il markup, ma il blocco, che viene memorizzato nella cache e qui c'è il problema. Non è possibile risolverlo con i tag cache, perché vengono invalidati solo se il nodo viene modificato nel database.
4k4,

@Vagner È possibile impostare la cache di un oggetto Block; La BlockBaseclasse ha anche i metodi necessari.
kiamlaluno

1
Per me return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];funziona benissimo per la memorizzazione nella cache per URL.
leymannx,

1
Sì, @leymannx, è semplice come questo. Questa discussione sembra ripensare il problema.
4k4

0

Il problema qui è che i contesti della cache non sono dichiarati nel posto giusto nella funzione build:

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

Se si chiama quel blocco su un non nodo, la funzione build restituisce un array vuoto, quindi non esiste un contesto cache per questo blocco e quel comportamento verrà memorizzato nella cache da drupal: la visualizzazione di questo blocco non verrà invalidata o resa correttamente.

La soluzione è solo l'inizializzazione di $ build con i contesti cache ogni volta:

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

0

Mi rendo conto di essere in ritardo per questa conversazione, ma il codice seguente ha funzionato per me:

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

meglio tardi che mai :)
Alex

0

Hai provato a implementare hook_block_view_BASE_BLOCK_ID_alter?

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.