Qual é a maneira correta de definir contextos de cache em blocos personalizados?


13

Ocorreu um problema em que um bloco que deveria ser único por página não é para usuários desconectados. O problema é um plug-in de bloco personalizado que tenho em uma página de pesquisa de visualizações que contém filtros personalizados (como uma substituição personalizada de filtros expostos. O bloco colocado por meio de / admin / structure / block).

Com base no que aprendi sobre o Drupal 8, adicionei os contextos de cache à minha matriz de compilação:

  public function build() {

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

  }

Mas parece que isso deve estar incorreto porque, quando desconectado, o bloco é armazenado em cache na primeira visualização e, quando o URL é alterado, ele não mostra uma nova versão do bloco.

Eu pensei que poderia ser a página de visualização que estava causando o problema, mas mesmo quando desativei o cache na página de visualização, o problema permaneceu.

Consegui corrigir o problema de várias maneiras, por exemplo, usando um gancho preprocess_block:

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

Mas me incomodou que eu não pudesse simplesmente colocar os contextos de cache na matriz de compilação do meu bloco.

Como meu bloco estende o BlockBase, decidi experimentar o método getCacheContexts (), especialmente porque vi que alguns módulos no núcleo estão fazendo dessa maneira.

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

Isso também corrigiu o problema, mas, curiosamente, quando eu mostro as variáveis ​​na função de bloco de pré-processo, elas não são exibidas em $ variable ['# cache'] ['contexts'], mas são exibidas nos elementos $ variable [' '] [' # cache '] [' contextos ']

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

Estou tentando descobrir como isso funciona e por que não estava funcionando na função de compilação.

Olhando para /core/modules/block/src/BlockViewBuilder.php na função viewMultiple (), parece que ele extrai as tags de cache da entidade e do plug-in:

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

Então, isso explica por que adicionar um método getCacheContexts () ao meu plug-in de bloco adiciona os contextos ao meu bloco. Além disso, olhando para o método preRender na mesma classe, parece que ele não usa a matriz de cache na função de construção de blocos, o que me confunde, pois parece que a maneira de adicionar cache no Drupal 8 é adicionar um #cache elemento para renderizar elementos.

Então, minha pergunta é:

1) Os contextos de cache adicionados diretamente à matriz em um plug-in de bloco são ignorados?

2) Em caso afirmativo, existe uma maneira de contornar isso, precisamos adicioná-lo a um elemento filho da matriz de compilação?

3) Se o contexto adicionado diretamente for ignorado, a adição de um getCacheContexts () é o caminho a seguir para plug-ins de bloco em módulos personalizados?


1
1) Não, o conteúdo do seu bloco é realmente um nível inferior e deve ser mesclado posteriormente. 2) Não é necessário porque 1, 3) A implementação de getCacheContexts () pode ser mais fácil / mais limpa, mas não deve ser necessária. Você menciona explicitamente usuários anônimos, tem certeza de que também não afeta usuários autenticados normais? O problema desaparece se você desativar o dynamic_page_cache? Algo estranho deve acontecer se isso afetar apenas usuários comuns, pois o cache da página interna sempre varia de acordo com os argumentos da URL / consulta.
22416 Berdir

1
Desativar o cache dinâmico da página não resolve o problema.
oknate

1
Hum, pode haver um problema com o fato de seu elemento de nível superior não conter nada, exceto #cache. Você já tentou definir #cache dentro de seu formulário? É a forma que precisa variar de acordo com essas e, como as tags de cache aparecem, isso deve funcionar. E se você usar seu formulário em um bloco ou outro local diferente, ele também deverá funcionar lá.
22416 Berdir

1
Eu rapidamente cortei o SyndicateBlock e usei este método build (): gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Colocando isso no meu site funciona perfeitamente, os contextos de cache são visíveis na tabela cache_render e o URI de solicitação correto é exibido. Você pode tentar o mesmo? Parece-me que algo muito estranho está acontecendo no seu site.
Berdir 19/12/16

2
Você usa o modelo de bloco padrão ou um modelo personalizado? Veja drupal.stackexchange.com/questions/217884/…
4k4

Respostas:


9

Na maioria dos casos, você apenas define o contexto do cache diretamente na matriz de renderização que retorna em seu método build ().

Finalmente descobri qual era o meu problema, com a ajuda de @Berdir e @ 4k4. Se você estiver usando um modelo personalizado, como block - myblock.html.twig, e gerar as variáveis ​​individualmente, como {{content.foo}}, em vez de todas ao mesmo tempo como {{content}}, ele ignorará seus contextos de cache passados ​​diretamente para sua matriz de criação de blocos, quando desconectados. Consulte Qual é a maneira correta de definir contextos de cache em blocos personalizados?

Então, para responder à pergunta original:

1) Contextos de cache passados ​​diretamente para um plug-in de bloco personalizado às vezes são ignorados. Você pode testar isso alterando o SyndicateBlock e, em seguida, criando um modelo personalizado no seu bloco de temas - syndicate.html.php, no qual você gera as variáveis ​​individualmente assim:

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

À medida que você altera os argumentos do URL, verá que o bloco não respeita o contexto do cache.

Agora, se você alterá-lo, produzindo todo o conteúdo como uma peça, funciona:

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

Agora, ele respeita o contexto do cache e o bloco é único por página.

2) Por enquanto, para contornar isso, você pode simplesmente exibir o que está no seu bloco no seu próprio modelo.

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

  }

Isso contorna as exceções de cache esotérico do módulo de bloco e seu formulário agora é único por página quando desconectado.

3) Você deve criar seu próprio modelo de tema para corrigir isso ou simplesmente adicionar um método para getCacheContexts () no seu plug-in de bloco personalizado? É melhor criar um novo modelo de tema, em vez de adicionar um método getCacheContexts () que substitua a ordem natural de integração de contextos de cache e pode quebrar os metadados mais profundamente em sua matriz de compilação.


Este é um resumo muito bom do problema. Mas acho que a conclusão em 3) é problemática, porque você não apenas quebra que seus próprios metadados de cache podem explodir, mas também o que pode estar mais profundo dentro da matriz de renderização e você pode não estar ciente disso.
4k4

Então você sugeriria a criação de um novo modelo de tema? Para preservar bolhas claras?
precisa

Sim, use o modelo de bloco apenas para adicionar itens ao exterior. Crie o interior do bloco em build (). Use modelos personalizados para isso ou use elementos de renderização, como uma tabela ou um modelo principal, como links.
4k4

OK, vou atualizar a resposta.
precisa

Ugh, eu não sabia que a perfuração de Twig ignoraria os metadados armazenáveis ​​em cache. Isso pode significar que precisamos usar nosso próprio método personalizado no final para detalhar (o que torna a extensão do galho inútil), para preservar os metadados enquanto desce para níveis mais baixos. Boa descoberta!
precisa saber é o seguinte

4

Para quem encontrar isso ...

O motivo pelo qual a renderização content(ou content|without()) funciona é que existe um elemento na matriz de renderização content['#cache']que contém todos os metadados armazenáveis ​​em cache do conteúdo.

Se você não permitir que isso seja renderizado em galho, contentou {{'#cache': content['#cache']|render }}a página não saberá que possui metadados armazenáveis ​​em cache (por exemplo, nunca borbulha).

Parece que você não está fazendo um galho personalizado. Se você estiver usando algo como Varnish, isso também pode ser um culpado para usuários anônimos.


3

Também deparei com esse problema e a solução alternativa que criei foi criar uma nova variável block_content com base em uma versão filtrada da variável de conteúdo principal, excluindo os campos personalizados que desejo processar manualmente:

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

Então, em vez de renderizar a variável "content.body" diretamente mais tarde, eu chamo:

{{ block_content }}

Se você deseja renderizar todos os campos individualmente, basta continuar adicionando-os ao filtro "sem" para que a renderização block_content não faça nada além de corrigir o cache.


0

O método mais fácil de conseguir isso é declarando e definindo o 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'];
  }

Confira a documentação do CacheableDependency , que deve conter tudo o que você precisa;)


Isso não funciona mais da maneira como os blocos são renderizados agora, consulte drupal.stackexchange.com/questions/288881/…
4k4
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.