Làm cách nào để thiết lập chính xác bộ đệm ẩn cho khối tùy chỉnh hiển thị nội dung tùy thuộc vào nút hiện tại?


19

Tôi có khối rất cơ bản này chỉ hiển thị ID của nút hiện tại.

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

}

Nhưng một khi được lưu trữ, khối vẫn giữ nguyên, bất kể tôi truy cập nút nào. Làm cách nào để tôi lưu chính xác kết quả vào mỗi ID nút?


1
Nhìn getCacheTags()từ BlockBase, bạn chỉ cần thêm một thẻ đại diện cho nút của bạn (nút: {nid}). Xin lỗi tôi đang vội, tôi có thể giải thích rõ hơn sau,
Vagner

Câu trả lời:


31

Đây là mã làm việc đầy đủ với ý kiến.

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

Tôi đã thử nó; nó hoạt động

Chỉ cần đặt mã vào một tệp có tên NodeCachedBlock.php trong thư mục mô-đun của bạn, thay đổi không gian tên của nó {module_name}, xóa bộ đệm và sử dụng nó.


Vì vậy, mẹo là để loại bỏ các #cachecài đặt trong chức năng xây dựng và chỉ cần thêm các chức năng công cộng?
Alex

3
Điều đó không quan trọng khi bạn đặt các thẻ và bối cảnh bộ đệm.
4k4

Chà, tôi nghĩ điều đó có ý nghĩa hơn, bởi vì chúng tôi đang xây dựng một khối, vì vậy khối cần phải được lưu trữ. Nếu bạn thay đổi khối của mình trong tương lai (nghĩa là đặt thêm một số yếu tố kết xuất), khối của bạn sẽ hoạt động.
Vagner

@ 4k4 url.path dường như cũng đã hoạt động. có gì khác biệt?
Alex

2
@Vagner: Đặt các thẻ / bối cảnh bộ đệm trong mảng kết xuất cũng không phải là ý tưởng tồi, bởi vì bạn có nó ở nơi dữ liệu của bạn, phụ thuộc vào nó. Và nó sẽ luôn nổi bong bóng, vì vậy bạn không phải lo lắng về các yếu tố ở trên. Btw. mã của bạn là tuyệt vời, giải thích các vấn đề bộ đệm rất tốt.
4k4

13

Cách dễ nhất để làm điều này là dựa vào hệ thống ngữ cảnh plugin / chặn.

Xem câu trả lời của tôi để biết cách tạo khối kéo nội dung nút hiện tại?

Bạn chỉ cần đặt một định nghĩa ngữ cảnh nút trong chú thích khối của bạn như thế này:

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

Và sau đó sử dụng nó như thế này: $this->getContextValue('node')

Điều tuyệt vời ở đây là Drupal sẽ chăm sóc bộ nhớ đệm cho bạn. Tự động. Bởi vì nó biết rằng bối cảnh nút mặc định (và theo như cốt lõi) là nút hiện tại. Và điều đó biết nó đến từ đâu, vì vậy bối cảnh bộ nhớ cache và thẻ bộ nhớ cache được thêm tự động.

Thông qua \Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()và các getCacheTags()phương thức tương ứng , BlockBase / lớp khối của bạn mở rộng từ đó và kế thừa các phương thức đó.


Bạn thay thế \Drupal::routeMatch()->getParameter('node')bằng $this->getContextValue('node')và bạn giải quyết toàn bộ vấn đề bộ đệm với một dòng mã? Tuyệt quá!
4k4

1
cảm ơn cho đến nay bạn có thể cung cấp một ví dụ mã đầy đủ?
Alex

@Alex: Tôi đã chỉnh sửa câu hỏi của bạn. Vui lòng kiểm tra và thay đổi mã nếu bạn tìm thấy bất kỳ lỗi nào.
4k4

@ 4k4 Tôi đã không dùng thử vì giải pháp khác cũng hoạt động
Alex


7

Nếu bạn lấy được lớp của plugin khối từ đó Drupal\Core\Block\BlockBase, bạn sẽ có hai phương pháp để đặt thẻ và bối cảnh bộ đệm.

  • getCacheTags()
  • getCacheContexts()

Ví dụ, khối mô-đun Sách thực hiện các phương thức đó như sau.

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

Khối mô-đun diễn đàn sử dụng mã sau đây.

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

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

Trong trường hợp của bạn, tôi sẽ sử dụng mã sau đây.

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

Bạn cũng có thể sử dụng phương pháp sau đây, để làm cho khối không thể điều khiển được (ngay cả khi tôi sẽ tránh nó). Nó có thể hữu ích trong các trường hợp khác, có thể.

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

Nhớ thêm use Drupal\Core\Cache\Cache;vào đầu tập tin, nếu bạn định sử dụng Cachelớp.


cảm ơn, nhưng trên / nút / 2, khối vẫn xuất ra 1 khi tôi truy cập nút / 1 ở vị trí đầu tiên, sau khi xóa bộ nhớ cache của tôi
Alex

2
Nếu bạn đang chỉnh sửa một mô-đun được kích hoạt, trước tiên bạn cần gỡ cài đặt nó trước khi chỉnh sửa nó. Xóa bộ nhớ cache là không đủ.
kiamlaluno

được, nhưng thêm maxAge 0 hoạt động, thật kỳ lạ!
Alex

Ngoài ra, lớp khối của bạn có sử dụng BlockBaselớp làm lớp cha không?
kiamlaluno

vâng, nó sử dụng nó
Alex

3

Khi bạn xây dựng một mảng kết xuất, luôn luôn đính kèm siêu dữ liệu chính xác:

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

Đây không phải là khối cụ thể và các phương thức phụ thuộc bộ đệm của bộ đệm getCacheTags (), getCacheContext () và getCacheMaxAge () không phải là sự thay thế. Chúng chỉ nên được sử dụng cho siêu dữ liệu bộ đệm bổ sung, không thể được phân phối thông qua mảng kết xuất.

Xem tài liệu:

"Điều quan trọng nhất là bạn thông báo cho API kết xuất về khả năng lưu trữ của một mảng kết xuất."

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

Xem ví dụ này về cách Drupal mong đợi một mảng kết xuất cung cấp siêu dữ liệu bộ đệm cần thiết khi tối ưu hóa bộ đệm ẩn thông qua tự động giữ chỗ và xây dựng lười biếng Vấn đề đặt thẻ bộ nhớ cache cụ thể của người dùng trên khối tùy chỉnh với ngữ cảnh người dùng


Tôi không nghĩ rằng có thể thiết lập bộ đệm của đối tượng Block. '#markup' chỉ là một đối tượng Phần tử kết xuất và không có lý do gì để đặt bối cảnh hoặc thẻ bộ đệm. Đối tượng khối cần xây dựng lại khi bộ đệm không hợp lệ.
Vagner

#markupcó thể được lưu trữ giống như bất kỳ phần tử kết xuất nào khác. Trong trường hợp này, nó không phải là đánh dấu, mà là khối, được lưu trữ và đây là vấn đề. Bạn không thể giải quyết nó bằng các thẻ bộ đệm, vì chúng chỉ bị vô hiệu, nếu nút được thay đổi trong cơ sở dữ liệu.
4k4

@Vagner Bạn có thể đặt bộ đệm của đối tượng Khối; Các BlockBaselớp có ngay cả những phương pháp cần thiết.
kiamlaluno

1
Đối với tôi return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];hoạt động siêu tốt cho mỗi bộ nhớ đệm URL.
leymannx

1
Vâng, @leymannx, nó đơn giản như thế này. Chủ đề này dường như lật đổ vấn đề.
4k4

0

Vấn đề ở đây là bối cảnh bộ đệm không được khai báo ở đúng nơi trong chức năng xây dựng:

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

Nếu bạn gọi khối đó trên một nút không, hàm xây dựng trả về một mảng trống, do đó không có bối cảnh bộ đệm cho khối này và hành vi đó sẽ được lưu trong bộ đệm bởi drupal: hiển thị của khối này sẽ không hợp lệ hoặc được hiển thị đúng.

Giải pháp chỉ là khởi tạo $ build với bối cảnh bộ đệm mọi lúc:

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

0

Tôi nhận ra rằng tôi đến trễ cuộc trò chuyện này, nhưng đoạn mã dưới đây đã làm việc cho tôi:

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,
    ));
  }
}

muộn còn hơn không bao giờ :)
Alex

0

Bạn đã thử triển khai hook_block_view_BASE_BLOCK_ID_alter chưa?

hàm hook_block_view_BASE_BLOCK_ID_alter (mảng & $ build, \ Drupal \ Core \ Block \ BlockPluginInterface $ block) {$ build ['# cache'] ['max-age'] = 0; }

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.