¿Cuál es la forma correcta de establecer contextos de caché en bloques personalizados?

13

Me he encontrado con un problema en el que un bloque que debería ser único por página no es para usuarios desconectados. El problema es un complemento de bloque personalizado que tengo en una página de búsqueda de vistas que contiene filtros personalizados (algo así como un reemplazo personalizado para filtros expuestos. El bloque se coloca a través de / admin / structure / block).

Según lo que aprendí sobre Drupal 8, agregué los contextos de caché a mi matriz de compilación:

  public function build() {

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

  }

Pero parece que esto debe ser incorrecto porque cuando se cierra la sesión, el bloque se almacena en caché en la primera vista, y cuando la URL cambia, no se muestra una nueva versión del bloque.

Pensé que podría ser la página de vista la que estaba causando el problema, pero incluso cuando desactivé el almacenamiento en caché en la página de vista, el problema persistió.

Pude solucionar el problema de varias maneras, por ejemplo, usando un gancho preprocess_block:

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

Pero me molestó que no pudiera simplemente poner los contextos de caché en la matriz de compilación de mi bloque.

Dado que mi bloque extiende BlockBase, decidí probar el método getCacheContexts (), especialmente porque vi que algunos módulos dentro del núcleo lo están haciendo de esta manera.

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

Esto también soluciona el problema, pero curiosamente, cuando imprimo las variables en la función de bloque de preproceso, estas no se muestran en $ variables ['# caché'] ['contextos'], pero sí se muestran en $ variables ['elementos '] [' # caché '] [' contextos ']

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

Estoy tratando de descubrir cómo funciona esto y por qué no funcionaba desde la función de compilación.

Al mirar /core/modules/block/src/BlockViewBuilder.php en la función viewMultiple (), parece que extrae las etiquetas de caché de la entidad y el complemento:

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

Eso explica por qué agregar un método getCacheContexts () a mi complemento de bloque agrega los contextos a mi bloque. Además, al observar el método preRender en la misma clase, parece que no usa la matriz de caché en la función de construcción de bloques, lo que me confunde, ya que parece que la forma de agregar el almacenamiento en caché en Drupal 8 es agregar un #caché elemento para representar elementos.

Entonces mi pregunta es,

1) ¿Se ignoran los contextos de caché agregados directamente en la matriz en un complemento de bloque?

2) Si es así, ¿hay alguna forma de evitarlo? ¿Necesitamos agregarlo a un elemento hijo de la matriz de compilación?

3) Si se ignora el contexto agregado directamente, ¿agregar el getCacheContexts () es el camino a seguir para los complementos de bloque en módulos personalizados?

oknate
fuente
1
1) No, su contenido de bloque está en realidad un nivel inferior y debe fusionarse más adelante. 2) No es necesario porque 1, 3) La implementación de getCacheContexts () puede ser más fácil / limpia pero no debería ser necesaria. Menciona explícitamente a los usuarios anónimos, ¿está seguro de que tampoco afecta a los usuarios autenticados normales? ¿El problema desaparece si deshabilita dynamic_page_cache? Algo extraño debe suceder si solo afecta a los usuarios, ya que la memoria caché interna de la página siempre varía según los argumentos de url / query de todos modos.
Berdir
1
Deshabilitar la memoria caché de página dinámica no soluciona el problema.
Oknate
1
Hm, podría ser un problema con el hecho de que su elemento de nivel superior no contiene nada excepto #caché. ¿Has intentado simplemente establecer #caché dentro de tu formulario? Es la forma que debe variar según esos, y dado que las etiquetas de caché aparecen, eso debería funcionar. Y si alguna vez usa su formulario en un bloque diferente u otro lugar, también debería funcionar allí.
Berdir
1
Rápidamente pirateé SyndicateBlock y usé este método build (): gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Colocar eso en mi sitio funciona bien, los contextos de caché son visibles en la tabla cache_render y se muestra el URI de solicitud correcto. ¿Puedes intentar lo mismo? Me parece que algo muy extraño está sucediendo en su sitio
Berdir
2
¿Utiliza la plantilla de bloque estándar o personalizada? Ver drupal.stackexchange.com/questions/217884/…
4k4

Respuestas:

9

En la mayoría de los casos, simplemente configura el contexto de caché directamente en la matriz de renderizado que devuelve en su método build ().

Finalmente encontré cuál era mi problema, con la ayuda de @Berdir y @ 4k4. Si está utilizando una plantilla personalizada, como block - myblock.html.twig y genera las variables individualmente, como {{content.foo}} en lugar de todas al mismo tiempo como {{content}}, ignora sus contextos de caché pasaron directamente a su conjunto de compilación de bloques, cuando se cerró la sesión. Consulte ¿Cuál es la forma correcta de establecer contextos de caché en bloques personalizados?

Entonces, para responder la pregunta original:

1) Los contextos de caché pasados ​​directamente a un complemento de bloque personalizado a veces se ignoran. Puede probar esto alterando el SyndicateBlock y luego creando una plantilla personalizada en su bloque de tema: syndicate.html.php en el que genera las variables individualmente de esta manera:

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

A medida que cambie los argumentos de la URL, verá que el bloque no respeta el contexto de caché.

Ahora, si lo cambia, genera todo el contenido como una pieza, funciona:

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

Ahora, respeta el contexto de caché, y el bloque es único por página.

2) Por ahora, para evitar esto, solo puede generar lo que está en su bloque en su propia plantilla.

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

  }

Esto evita las excepciones de almacenamiento en caché esotérico del módulo de bloque y su formulario ahora es único por página cuando se cierra la sesión.

3) ¿Debería crear su propia plantilla de tema para solucionar esto, o simplemente agregar un método para getCacheContexts () en su complemento Block personalizado? Es mejor crear una nueva plantilla de tema, en lugar de agregar un método getCacheContexts () que anula el orden natural de los contextos de caché y puede romper los metadatos más profundamente en su matriz de compilación.

oknate
fuente
Este es un muy buen resumen del problema. Pero creo que la conclusión en 3) es problemática, porque no solo rompe que sus propios metadatos de caché pueden burbujear, sino también lo que puede estar más profundo dentro de la matriz de renderizado y puede que no sea consciente.
4k4
¿Entonces sugeriría crear una nueva plantilla de tema? Para preservar el burbujeo claro?
Oknate
Sí, use la plantilla de bloque solo para agregar cosas al exterior. Construye el interior del bloque en build (). Use plantillas personalizadas para esto o use elementos de representación, como una tabla o una plantilla central como enlaces.
4k4
OK, actualizaré la respuesta.
Oknate
Uf, no me di cuenta de que la perforación de Twig ignoraría los metadatos almacenables en caché. Esto podría significar que necesitamos usar nuestro propio método personalizado al final para profundizar (lo que hace que la extensión de la ramita sea inútil) para preservar los metadatos mientras bajamos a niveles más bajos. ¡Buen descubrimiento!
LionsAd
4

Para cualquiera que encuentre esto ...

La razón por la que la representación content(o content|without()) funciona es que hay un elemento en la matriz de representación content['#cache']que contiene todos los metadatos almacenables en caché para el contenido.

Si no permite que esto se represente en ramita, contento bien {{'#cache': content['#cache']|render }}, la página no sabe que tiene metadatos almacenables en caché (por ejemplo, nunca aparece).

Sin embargo, parece que no estás haciendo una rama personalizada. Si está usando algo como Varnish, eso también podría ser un culpable para los usuarios anónimos.

tela asargada
fuente
3

También me encontré con este problema y la solución que se me ocurrió fue crear una nueva variable block_content basada en una versión filtrada de la variable de contenido principal, excluyendo cualquier campo personalizado que desee representar manualmente:

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

Luego, en lugar de representar la variable "content.body" directamente más tarde, llamo:

{{ block_content }}

Si desea renderizar cada campo individualmente, puede seguir agregándolos al filtro "sin" para que la representación block_content no haga nada más que arreglar el almacenamiento en caché.

Ryan Barkley
fuente
0

El método más fácil para lograr esto es declarando y definiendo el getCacheContexts()método


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

Consulte la documentación de CacheableDependency , debe contener todo lo que necesita;)

Ben Cassinat
fuente
Esto ya no funciona de la forma en que se representan los bloques ahora, consulte drupal.stackexchange.com/questions/288881/…
4k4