Jaki jest prawidłowy sposób ustawiania kontekstów pamięci podręcznej w niestandardowych blokach?

13

Wystąpił problem polegający na tym, że blok, który powinien być unikalny na stronie, nie jest dla wylogowanych użytkowników. Problemem jest niestandardowa wtyczka blokowa, którą mam na stronie wyszukiwania widoków, która zawiera niestandardowe filtry (coś w rodzaju niestandardowego zamiennika dla odsłoniętych filtrów. Blok umieszczony przez / admin / structure / block).

Na podstawie tego, czego dowiedziałem się o Drupal 8, dodałem konteksty pamięci podręcznej do mojej tablicy kompilacji:

  public function build() {

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

  }

Wygląda jednak na to, że musi to być niepoprawne, ponieważ po wylogowaniu blok zostanie zapisany w pamięci podręcznej przy pierwszym widoku, a gdy zmieni się adres URL, nie będzie pokazywał nowej wersji bloku.

Myślałem, że przyczyną może być strona widoku, ale nawet po wyłączeniu buforowania na stronie widoku problem pozostał.

Byłem w stanie rozwiązać problem na kilka sposobów, na przykład za pomocą haka preprocess_block:

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

Ale przeszkadzało mi to, że nie mogłem po prostu umieścić kontekstów pamięci podręcznej w tablicy kompilacji mojego bloku.

Ponieważ mój blok rozszerza BlockBase, postanowiłem wypróbować metodę getCacheContexts (), zwłaszcza że widziałem, że niektóre moduły w rdzeniu robią to w ten sposób.

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

To również rozwiązało problem, ale co ciekawe, kiedy wypisuję zmienne w funkcji bloku przetwarzania wstępnego, nie są one wyświetlane w $ zmiennych ['# cache'] ['kontekstach'], ale pokazują się w elementach $ zmiennych [' '] [' # cache '] [' konteksty ']

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

Próbuję dowiedzieć się, jak to działa i dlaczego nie działa z funkcji kompilacji.

Patrząc na /core/modules/block/src/BlockViewBuilder.php w funkcji viewMultiple (), wygląda na to, że pobiera tagi pamięci podręcznej z encji i wtyczki:

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

To wyjaśnia, dlaczego dodanie metody getCacheContexts () do mojej wtyczki bloku dodaje konteksty do mojego bloku. Patrząc na metodę preRender w tej samej klasie, wygląda na to, że nie używa ona tablicy pamięci podręcznej w funkcji budowania bloków, co mnie dezorientuje, ponieważ wydaje się, że sposobem na dodanie buforowania w Drupal 8 jest dodanie pamięci podręcznej # element do renderowania elementów.

Więc moje pytanie brzmi:

1) Czy konteksty pamięci podręcznej dodawane bezpośrednio do tablicy w wtyczce bloku są ignorowane?

2) Jeśli tak, to czy można to obejść, czy musimy dodać go do elementu potomnego tablicy kompilacji?

3) Jeśli kontekst dodany bezpośrednio jest ignorowany, czy dodanie getCacheContexts () jest sposobem na wtyczki blokowe w niestandardowych modułach?

oknate
źródło
1
1) Nie, zawartość bloków jest w rzeczywistości niższa i powinna zostać scalona później. 2) Niepotrzebne, ponieważ 1, 3) Implementacja getCacheContexts () może być łatwiejsza / czystsza, ale nie powinna być wymagana. Wyraźnie wspominasz o anonimowych użytkownikach, czy jesteś pewien, że nie wpływa to również na zwykłych uwierzytelnionych użytkowników? Czy problem zniknie, jeśli wyłączysz pamięć podręczną dynamic_page_cache? Coś dziwnego musi się wydarzyć, jeśli wpływa tylko na użytkowników anon, ponieważ wewnętrzna pamięć podręczna strony zawsze różni się w zależności od argumentów adresu URL / zapytania.
Berdir,
1
Wyłączenie dynamicznego bufora strony nie rozwiązuje problemu.
oknate
1
Hm, może to być problem z faktem, że element najwyższego poziomu nie zawiera niczego poza #cache. Czy próbowałeś po prostu ustawić # pamięć podręczną w swoim formularzu? Jest to forma, która musi się różnić w zależności od nich, a ponieważ znaczniki pamięci podręcznej pękają, powinno to po prostu działać. A jeśli kiedykolwiek użyjesz swojego formularza w innym bloku lub innym miejscu, powinno tam również działać.
Berdir,
1
Szybko zhakowałem SyndicateBlock i użyłem tej metody build (): gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Umieszczenie w mojej witrynie działa dobrze, konteksty pamięci podręcznej są widoczne w tabeli cache_render i wyświetlany jest poprawny identyfikator URI żądania. Czy możesz spróbować tego samego? Wydaje mi się, że na twojej stronie dzieje się coś bardzo dziwnego
Berdir,
2
Czy używasz standardowego szablonu bloku, czy niestandardowego? Zobacz drupal.stackexchange.com/questions/217884/…
4k4

Odpowiedzi:

9

W większości przypadków kontekst pamięci podręcznej jest ustawiany bezpośrednio na tablicy renderowania zwracanej w metodzie build ().

W końcu odkryłem, na czym polegał mój problem, przy pomocy @Berdir i @ 4k4. Jeśli używasz niestandardowego szablonu, takiego jak blok - myblock.html.twig, i wyprowadzasz zmienne osobno, takie jak {{content.foo}} zamiast wszystkich w tym samym czasie jak {{content}}, ignoruje konteksty pamięci podręcznej przekazywane po wylogowaniu bezpośrednio do tablicy kompilacji bloku. Zobacz Jaki jest prawidłowy sposób ustawiania kontekstów pamięci podręcznej w niestandardowych blokach?

Aby odpowiedzieć na pierwotne pytanie:

1) Konteksty pamięci podręcznej przekazywane bezpośrednio do niestandardowej wtyczki blokowej są czasami ignorowane. Możesz to przetestować, zmieniając SyndicateBlock , a następnie tworząc niestandardowy szablon w bloku motywu - syndicate.html.php, w którym wypisujesz zmienne indywidualnie w następujący sposób:

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

Podczas zmiany argumentów adresu URL zobaczysz, że blok nie szanuje kontekstu pamięci podręcznej.

Teraz, jeśli zmienisz, wypisuje całą zawartość jako kawałek, działa:

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

Teraz szanuje kontekst pamięci podręcznej, a blok jest unikalny dla każdej strony.

2) Na razie, aby obejść ten problem, możesz po prostu wypisać zawartość bloku w swoim własnym szablonie.

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

  }

To omija ezoteryczne wyjątki buforowania modułu blokowego, a po wylogowaniu formularz jest teraz unikalny dla każdej strony.

3) Czy należy utworzyć własny szablon motywu, aby to naprawić, czy po prostu dodać metodę getCacheContexts () we własnej wtyczce Block? Lepiej jest utworzyć nowy szablon kompozycji, niż dodawać metodę getCacheContexts (), która zastępuje naturalną kolejność tworzenia kontekstów pamięci podręcznej i może głębiej rozbić metadane w tablicy kompilacji.

oknate
źródło
To bardzo dobre podsumowanie problemu. Ale myślę, że wniosek w punkcie 3) jest problematyczny, ponieważ łamiesz nie tylko to, że twoje własne metadane pamięci podręcznej mogą pęknąć, ale także to, co może znajdować się głębiej w tablicy renderowania i możesz nie być tego świadomy.
4k4
Sugerowałbyś więc utworzenie nowego szablonu motywu? Aby zachować wyraźne bulgotanie?
oknate
Tak, użyj szablonu bloku tylko w celu dodania elementów na zewnątrz. Zbuduj wnętrze bloku w build (). Użyj do tego niestandardowych szablonów lub elementów renderowania, takich jak tabela lub szablon podstawowy, np. Łącza.
4k4
OK, zaktualizuję odpowiedź.
oknate
Ugh, nie zdawałem sobie sprawy, że wiercenie Twiga zignoruje metadane w pamięci podręcznej. Może to oznaczać, że musimy w końcu użyć naszej własnej metody niestandardowej, aby wykonać drążenie (co sprawia, że ​​rozszerzenie gałązki jest bezużyteczne), aby zachować metadane podczas schodzenia na niższe poziomy. Dobre znalezisko!
LionsAd
4

Dla każdego, kto znajdzie to ...

Powodem działania renderowania content(lub content|without()) jest to, że w tablicy renderowania znajduje się element content['#cache']zawierający wszystkie buforowane metadane dla treści.

Jeśli nie pozwalają na to, aby być renderowane w gałązki, albo contentalbo {{'#cache': content['#cache']|render }}, strona nie wie, że ma Cacheable metadane (np. Nigdy nie propaguje w górę).

Wygląda na to, że nie robisz niestandardowej gałązki. Jeśli używasz czegoś takiego jak Varnish, może to być również winowajca dla anonimowych użytkowników.

diagonal
źródło
3

Natknąłem się również na ten problem, a obejściem tego problemu było utworzenie nowej zmiennej block_content w oparciu o przefiltrowaną wersję głównej zmiennej treści, z wyłączeniem pól niestandardowych, które chcę renderować ręcznie:

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

Następnie zamiast renderować zmienną „content.body” bezpośrednio później, wywołuję:

{{ block_content }}

Jeśli chcesz renderować każde pole osobno, możesz po prostu dodawać je do filtru „bez”, aby renderowanie zawartości bloku nie powodowało niczego poza poprawnym buforowaniem.

Ryan Barkley
źródło
0

Najłatwiejszym sposobem osiągnięcia tego jest zadeklarowanie i zdefiniowanie getCacheContexts()metody


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

Sprawdź dokumentację CacheableDependency , powinna ona zawierać wszystko, czego potrzebujesz;)

Ben Cassinat
źródło
To już nie działa, jak teraz są renderowane bloki, patrz drupal.stackexchange.com/questions/288881/…
4k4