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?

oknate
fonte
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.

oknate
fonte
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.

sarja
fonte
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.

Ryan Barkley
fonte
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;)

Ben Cassinat
fonte
Isso não funciona mais da maneira como os blocos são renderizados agora, consulte drupal.stackexchange.com/questions/288881/…
4k4