Apa cara yang benar untuk mengatur konteks cache pada blok khusus?

13

Saya telah mengalami masalah di mana blok yang seharusnya unik per halaman bukan untuk pengguna yang keluar. Masalahnya adalah plugin blok khusus yang saya miliki pada halaman pencarian tampilan yang berisi filter khusus (semacam pengganti kustom untuk filter terbuka. Blok ditempatkan melalui / admin / struktur / blok).

Berdasarkan apa yang telah saya pelajari tentang Drupal 8, saya menambahkan konteks cache ke array build saya:

  public function build() {

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

  }

Tapi sepertinya ini pasti salah karena ketika keluar, blok akan di-cache pada tampilan pertama, dan ketika url berubah, itu tidak menunjukkan versi baru dari blok.

Saya pikir itu mungkin halaman tampilan yang menyebabkan masalah, tetapi bahkan ketika saya mematikan caching pada halaman tampilan, masalah tetap ada.

Saya dapat memperbaiki masalah ini beberapa cara, misalnya, dengan menggunakan kait preprocess_block:

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

Tapi itu mengganggu saya, saya tidak bisa begitu saja memasukkan konteks cache ke dalam array build blok saya.

Karena blok saya memperluas BlockBase, saya memutuskan untuk mencoba metode getCacheContexts (), terutama karena saya melihat beberapa modul di dalam core melakukannya dengan cara ini.

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

Ini juga memperbaiki masalah, tetapi yang menarik, ketika saya menampilkan variabel dalam fungsi blok preproses, ini tidak ditampilkan dalam $ variabel ['# cache'] ['konteks'], tetapi mereka ditampilkan dalam elemen $ variabel [' '] [' # cache '] [' contexts ']

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

Saya mencoba mencari tahu bagaimana ini bekerja, dan mengapa itu tidak berfungsi dari fungsi build.

Melihat /core/modules/block/src/BlockViewBuilder.php pada fungsi viewMultiple (), sepertinya ia menarik tag cache dari entitas dan plugin:

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

Jadi itu menjelaskan mengapa menambahkan metode getCacheContexts () ke plugin blok saya menambahkan konteks ke blok saya. Juga, melihat metode preRender di kelas yang sama, sepertinya itu tidak menggunakan array cache di fungsi blok bangunan, yang membingungkan saya, karena tampaknya cara untuk menambahkan caching di Drupal 8 adalah menambahkan #cache elemen untuk membuat elemen.

Jadi pertanyaan saya adalah,

1) Apakah konteks cache ditambahkan langsung pada array di plugin blok diabaikan?

2) Jika demikian, apakah ada cara lain, apakah kita perlu menambahkannya ke elemen turunan build?

3) Jika konteks yang ditambahkan secara langsung diabaikan, apakah menambahkan getCacheContexts () cara untuk mencari plugin blok dalam modul khusus?

oknate
sumber
1
1) Tidak, konten blokir Anda sebenarnya level bawah dan harus digabungkan nanti. 2) Tidak diperlukan karena 1, 3) Menerapkan getCacheContexts () dapat lebih mudah / bersih tetapi tidak diperlukan. Anda secara eksplisit menyebut pengguna anonim, apakah Anda yakin itu juga tidak memengaruhi pengguna terotentikasi normal? Apakah masalah hilang jika Anda menonaktifkan dynamic_page_cache? Sesuatu yang aneh harus terjadi jika hanya memengaruhi pengguna, karena cache halaman internal selalu bervariasi menurut url / kueri argumen.
Berdir
1
Menonaktifkan cache halaman dinamis tidak memperbaiki masalah.
oknate
1
Hm, Mungkin ada masalah dengan fakta bahwa elemen tingkat atas Anda tidak mengandung apa pun kecuali #cache. Sudahkah Anda mencoba mengatur #cache di dalam formulir Anda? Ini bentuk yang perlu divariasi oleh mereka, dan karena tag cache muncul, itu hanya akan berfungsi. Dan jika Anda pernah menggunakan formulir Anda di blok yang berbeda atau tempat lain, itu juga harus berfungsi di sana.
Berdir
1
Saya dengan cepat meretas SyndicateBlock dan menggunakan metode build () ini: gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Menempatkan itu di situs saya berfungsi dengan baik, konteks cache terlihat di tabel cache_render dan URI permintaan yang benar ditampilkan. Bisakah Anda mencoba yang sama? Menurut saya sesuatu yang sangat aneh sedang terjadi di situs Anda
Berdir
2
Apakah Anda menggunakan templat blok standar atau yang khusus? Lihat drupal.stackexchange.com/questions/217884/…
4k4

Jawaban:

9

Dalam kebanyakan kasus, Anda cukup mengatur konteks cache secara langsung pada array render yang Anda kembalikan dalam metode build () Anda.

Saya akhirnya menemukan apa masalah saya, dengan bantuan @Berdir dan @ 4k4. Jika Anda menggunakan templat khusus, seperti blok - myblock.html.twig dan Anda menampilkan variabel satu per satu, seperti {{content.foo}} alih-alih semuanya pada saat bersamaan seperti {{content}}, ia mengabaikan konteks cache Anda diteruskan langsung ke array build blok Anda, ketika keluar. Lihat Apa cara yang benar untuk mengatur konteks cache pada blok khusus?

Jadi, untuk menjawab pertanyaan awal:

1) Konteks cache yang diteruskan langsung ke plugin blok khusus terkadang diabaikan. Anda dapat menguji ini dengan mengubah SyndicateBlock , dan kemudian membuat templat kustom di blok tema Anda - syndicate.html.php di mana Anda menampilkan variabel secara individual seperti ini:

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

Saat Anda mengubah argumen url, Anda akan melihat blok tidak menghormati konteks cache.

Sekarang jika Anda mengubahnya mengeluarkan semua konten sebagai bagian, itu berfungsi:

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

Sekarang, itu menghormati konteks cache, dan blok itu unik per halaman.

2) Untuk saat ini, untuk mengatasinya, Anda bisa menampilkan apa yang ada di blok Anda di template itu sendiri.

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

  }

Ini menghindari pengecualian caching esoterik modul blok dan formulir Anda sekarang unik per halaman saat keluar.

3) Haruskah Anda membuat templat tema Anda sendiri untuk memperbaikinya, atau cukup tambahkan metode untuk getCacheContexts () di plugin Blok khusus Anda? Lebih baik untuk membuat templat tema baru, daripada menambahkan metode getCacheContexts () yang mengesampingkan urutan alami penggelembungan konteks cache, dan dapat memecah metadata lebih dalam di array build Anda.

oknate
sumber
Ini adalah ringkasan masalah yang sangat bagus. Tapi saya pikir kesimpulan dalam 3) bermasalah, karena Anda tidak hanya memecahkan bahwa metadata cache Anda sendiri dapat muncul, tetapi juga apa yang mungkin lebih dalam di dalam array render dan Anda mungkin tidak menyadarinya.
4k4
Jadi, Anda akan menyarankan membuat templat tema baru? Untuk mempertahankan gelembung yang jelas?
oknate
Ya, gunakan templat blokir saja untuk menambahkan sesuatu ke luar. Bangun bagian dalam blok di build (). Gunakan templat khusus untuk ini atau gunakan elemen render, seperti tabel atau templat inti seperti tautan.
4k4
OK, saya akan memperbarui jawabannya.
oknate
Ugh, saya tidak menyadari bahwa pengeboran Twig akan mengabaikan metadata yang dapat di-cache. Ini mungkin berarti bahwa kita perlu menggunakan metode khusus kita sendiri pada akhirnya untuk mengebor (yang menjadikan ekstensi ranting tidak berguna) sehingga kita mempertahankan metadata saat turun ke level yang lebih rendah. Bagus temukan!
LionsAd
4

Bagi siapa pun yang menemukan ini ...

Alasan rendering content(atau content|without()) berfungsi adalah bahwa ada elemen dalam array render content['#cache']yang berisi semua metadata yang dapat di-cache untuk konten.

Jika Anda tidak mengizinkan ini untuk dirender dalam ranting, salah satu contentatau {{'#cache': content['#cache']|render }}, halaman tidak tahu itu memiliki metadata yang dapat di-cache (mis. Itu tidak pernah muncul).

Sepertinya Anda tidak melakukan ranting khusus. Jika Anda menggunakan sesuatu seperti Varnish, itu juga bisa menjadi penyebab bagi pengguna anonim.

kain kepar
sumber
3

Saya juga mengalami masalah ini dan solusinya adalah membuat variabel block_content baru berdasarkan versi yang disaring dari variabel konten utama tidak termasuk bidang kustom yang ingin saya render secara manual:

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

Kemudian alih-alih merender variabel "content.body" secara langsung nanti, saya memanggil:

{{ block_content }}

Jika Anda ingin merender setiap bidang secara individual, Anda bisa terus menambahkannya ke filter "tanpa" sehingga rendering block_content tidak melakukan apa pun kecuali memperbaiki caching.

Ryan Barkley
sumber
0

Metode yang lebih mudah untuk mencapai ini adalah dengan mendeklarasikan dan mendefinisikan getCacheContexts()metode


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

Lihat dokumentasi CacheableDependency , itu harus berisi semua yang Anda butuhkan;)

Ben Cassinat
sumber
Ini tidak berfungsi lagi seperti yang diberikan blok sekarang, lihat drupal.stackexchange.com/questions/288881/…
4k4