Bagaimana cara saya mengatur cache dengan benar untuk blok khusus saya yang menampilkan konten tergantung pada node saat ini?

19

Saya memiliki blok yang sangat mendasar ini yang hanya menunjukkan ID node saat ini.

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\ExampleEmptyBlock.
 */

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * @Block(
 *   id = "example_empty",
 *   admin_label = @Translation("Example: empty block")
 * )
 */
class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $node = \Drupal::routeMatch()->getParameter('node');
    $build = array();

    if ($node) {
      $config = \Drupal::config('system.site');

      $build = array(
        '#type' => 'markup',
        '#markup' => '<p>' . $node->id() . '<p>',
        '#cache' => array(
          'tags' => $this->getCacheTags(),
          'contexts' => $this->getCacheContexts(),
        ),
      );
    }

    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

}

Tapi begitu di-cache, blok tetap sama, terlepas dari simpul mana yang saya kunjungi. Bagaimana cara menyimpan hasil per ID node dengan benar?

Alex
sumber
1
Lihat getCacheTags()dari BlockBase, Anda hanya perlu menambahkan tag yang mewakili simpul Anda (simpul: {nid}). Maaf saya sedang terburu-buru sekarang, saya bisa menjelaskan lebih baik nanti,
Vagner

Jawaban:

31

Ini adalah kode kerja lengkap dengan komentar.

namespace Drupal\module_name\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * Provides a Node cached block that display node's ID.
 *
 * @Block(
 *   id = "node_cached_block",
 *   admin_label = @Translation("Node Cached")
 * )
 */
class NodeCachedBlock extends BlockBase {
  public function build() {
    $build = array();
    //if node is found from routeMatch create a markup with node ID's.
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      $build['node_id'] = array(
        '#markup' => '<p>' . $node->id() . '<p>',
      );
    }
    return $build;
  }

  public function getCacheTags() {
    //With this when your node change your block will rebuild
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      //if there is node add its cachetag
      return Cache::mergeTags(parent::getCacheTags(), array('node:' . $node->id()));
    } else {
      //Return default tags instead.
      return parent::getCacheTags();
    }
  }

  public function getCacheContexts() {
    //if you depends on \Drupal::routeMatch()
    //you must set context of this block with 'route' context tag.
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }
}

Saya mengujinya; berhasil.

Cukup masukkan kode dalam file bernama NodeCachedBlock.php di folder modul Anda, ubah namespace {module_name}, kosongkan cache dan gunakan.

Vagner
sumber
jadi triknya adalah menghapus #cachepengaturan dalam fungsi build dan hanya menambahkan fungsi publik?
Alex
3
Tidak masalah di mana Anda mengatur tag dan konteks cache.
4k4
Yah, saya pikir itu lebih masuk akal, karena kita sedang membangun blok, jadi blok perlu di-cache. Jika Anda mengubah blok Anda di masa mendatang (yaitu meletakkan beberapa elemen render tambahan), blok Anda akan berfungsi.
Vagner
@ 4k4 url.path tampaknya juga berfungsi. apa bedanya?
Alex
2
@ Wagner: Menempatkan tag / konteks cache dalam array render juga bukan ide yang buruk, karena Anda memilikinya di mana data Anda berada, yang bergantung padanya. Dan itu akan selalu muncul, sehingga Anda tidak perlu khawatir tentang elemen-elemen yang ada di atas. Btw. kode Anda hebat, tidak menjelaskan masalah caching dengan sangat baik.
4k4
13

Cara termudah untuk melakukan ini adalah dengan mengandalkan sistem konteks plugin / blok.

Lihat jawaban saya untuk Bagaimana cara membuat blok yang menarik konten simpul saat ini?

Anda hanya perlu meletakkan definisi konteks simpul di anotasi blok Anda seperti ini:

*   context = {
*     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
*   }

Dan kemudian gunakan seperti ini: $this->getContextValue('node')

Yang menyenangkan tentang ini adalah bahwa Drupal akan mengurus caching untuk Anda. Secara otomatis. Karena ia tahu bahwa konteks default (dan sejauh core saja) node adalah node saat ini. Dan yang tahu dari mana asalnya, sehingga konteks cache dan tag cache ditambahkan secara otomatis.

Melalui \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()dan getCacheTags()metode yang sesuai , BlockBase / kelas blok Anda memanjang dari itu dan mewarisi metode tersebut.

Berdir
sumber
Anda ganti \Drupal::routeMatch()->getParameter('node')dengan $this->getContextValue('node')dan Anda menyelesaikan seluruh masalah caching dengan satu baris kode? Bagus!
4k4
1
terima kasih sejauh ini! dapatkah Anda memberikan contoh kode lengkap?
Alex
@ Alex: Saya mengedit pertanyaan Anda. Periksa dan ubah kode jika Anda menemukan kesalahan.
4k4
@ 4k4 Saya tidak mencobanya karena solusi lain juga berfungsi
Alex
@Alex
leymannx
7

Jika Anda berasal dari kelas plugin blok Drupal\Core\Block\BlockBaseAnda, Anda akan memiliki dua metode untuk mengatur tag dan konteks cache.

  • getCacheTags()
  • getCacheContexts()

Sebagai contoh, blok modul Buku mengimplementasikan metode-metode tersebut sebagai berikut.

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['route.book_navigation']);
  }

Blok modul Forum menggunakan kode berikut.

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    return Cache::mergeTags(parent::getCacheTags(), ['node_list']);
  }

Dalam kasus Anda, saya akan menggunakan kode berikut.

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

Anda juga dapat menggunakan metode berikut ini, untuk membuat blok tidak mudah terbakar sama sekali (bahkan jika saya akan menghindarinya). Mungkin bermanfaat dalam kasus lain, mungkin.

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return 0;
  }

Ingatlah untuk menambahkan use Drupal\Core\Cache\Cache;di bagian atas file, jika Anda akan menggunakan Cachekelas.

kiamlaluno
sumber
terima kasih, tetapi pada / node / 2 blok masih menampilkan 1 ketika saya mengunjungi node / 1 di tempat pertama, setelah membersihkan cache saya
Alex
2
Jika Anda mengedit modul yang diaktifkan, Anda harus mencopotnya terlebih dahulu, sebelum mengeditnya. Membersihkan cache tidak cukup.
kiamlaluno
oke, tapi menambahkan maxAge 0 berfungsi, anehnya!
Alex
Juga, apakah kelas blok Anda menggunakan BlockBasekelas sebagai kelas induk?
kiamlaluno
ya itu menggunakannya
Alex
3

Saat Anda membuat array render, selalu lampirkan metadata yang benar:

use Drupal\Core\Cache\Cache;

$build['node_id'] = [
  '#markup' => '<p>' . $node->id() . '<p>',
  '#cache' => [
    'tags' => $node->getCacheTags(),
    // add a context if the node is dependent on the route match
    'contexts' => ['route'],
  ],
];

Ini bukan blok spesifik dan metode blok dependensi cache getCacheTags (), getCacheContext () dan getCacheMaxAge () bukan pengganti. Mereka seharusnya hanya digunakan untuk metadata cache tambahan, yang tidak dapat dikirim melalui array render.

Lihat dokumentasi:

"Sangat penting bagi Anda untuk memberi tahu Render API tentang cacheability dari array render."

https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays

Lihat contoh ini bagaimana Drupal mengharapkan array render untuk menyediakan metadata cache yang diperlukan saat mengoptimalkan caching melalui auto-placeholdering dan lazy-building Masalah pengaturan tag cache spesifik pengguna pada blok khusus dengan konteks pengguna

4k4
sumber
Saya tidak berpikir itu dapat mengatur cache objek Blokir. '#markup' hanyalah objek Elemen Render dan tidak ada alasan untuk mengatur konteks atau tag cache. Objek blokir yang perlu dibangun kembali ketika cache tidak valid.
Vagner
#markupdapat di-cache sama dengan elemen render lainnya. Dalam hal ini bukan markup, tetapi blok, yang di-cache dan di sini masalahnya. Anda tidak dapat menyelesaikannya dengan tag cache, karena mereka hanya akan batal, jika node diubah dalam database.
4k4
@ Wagner Anda dapat mengatur cache dari objek Block; The BlockBasekelas memiliki bahkan metode yang diperlukan.
kiamlaluno
1
Bagi saya return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];berfungsi sangat baik untuk setiap caching URL.
leymannx
1
Ya, @leymannx, sesederhana ini. Utas ini tampaknya terlalu memikirkan masalah.
4k4
0

Masalahnya di sini adalah bahwa konteks cache tidak menyatakan di tempat yang tepat di fungsi build:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');
   $build = array();

   if ($node) {
    $config = \Drupal::config('system.site');

    $build = array(
    '#type' => 'markup',
    '#markup' => '<p>' . $node->id() . '<p>',
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );
 }
 return $build;
 }
}

Jika Anda memanggil blok itu di non node, fungsi build mengembalikan array kosong, jadi tidak ada konteks cache untuk blok ini dan perilaku itu akan di-cache oleh drupal: tampilan blok ini tidak akan benar-benar batal atau dibuat.

Solusi hanyalah menginisialisasi $ build dengan konteks cache setiap kali:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');

    $build = array(
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );

   if ($node) {
    $config = \Drupal::config('system.site');

    $build['#markup'] = '<p>' . $node->id() . '<p>';
    $build['#type'] = 'markup';
    }
 return $build;
 }
}
Tipu daya
sumber
0

Saya menyadari bahwa saya terlambat untuk percakapan ini, tetapi kode di bawah ini berfungsi untuk saya:

class ExampleBlock extends BlockBase
{

  public function build()
  {
    $lcContent = '';

    $loNode = \Drupal::routeMatch()->getParameter('node');

    if (!$loNode)
    {
      return (array(
        '#type' => 'markup',
        '#cache' => array('max-age' => 0),
        '#markup' => $lcContent,
      ));
    }

    $lcContent .= "<div id='example_block' style='overflow: hidden; clear: both;'>\n";
    $lcContent .= $loNode->id();
    $lcContent .= "</div>\n";

    return (array(
      '#type' => 'markup',
      '#cache' => array('max-age' => 0),
      '#markup' => $lcContent,
    ));
  }
}
Eddie Fann
sumber
lebih baik terlambat daripada tidak pernah :)
Alex
0

Sudahkah Anda mencoba menerapkan hook_block_view_BASE_BLOCK_ID_alter?

function hook_block_view_BASE_BLOCK_ID_alter (array & $ build, \ Drupal \ Core \ Block \ BlockPluginInterface $ block) {$ build ['# cache'] ['max-age'] = 0; }

Bolleddula Sambasiva Rao
sumber