현재 노드에 따라 컨텐츠를 표시하는 사용자 정의 블록에 대한 캐싱을 올바르게 설정하려면 어떻게해야합니까?


19

현재 노드의 ID를 보여주는 매우 기본적인 블록이 있습니다.

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

}

그러나 일단 캐시되면 방문한 노드에 관계없이 블록이 동일하게 유지됩니다. 노드 ID 당 결과를 올바르게 캐시하려면 어떻게해야합니까?


1
getCacheTags()({꾸벅 꾸벅} 노드) BlockBase에서, 당신은 당신의 노드를 나타냅니다 태그를 추가 할 필요가있다. 지금 서둘러서 죄송합니다. 나중에 더 잘 설명 할 수 있습니다.
Vagner

답변:


31

이것은 주석이 달린 완전한 작업 코드입니다.

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

나는 그것을 테스트했다; 효과가있다.

모듈 폴더에 NodeCachedBlock.php라는 파일에 코드를 넣고 네임 스페이스 {module_name}을 변경하고 캐시를 지우고 사용하십시오.


트릭은 #cache빌드 기능에서 설정 을 제거 하고 공개 기능을 추가하는 것입니까?
Alex

3
캐시 태그와 컨텍스트를 어디에 설정했는지는 중요하지 않습니다.
4k4

글쎄, 우리는 블록을 구축하고 있기 때문에 더 이해가가 능하다고 생각하므로 블록을 캐시해야합니다. 나중에 블록을 변경하면 (즉, 추가 렌더링 요소를 추가하면) 블록이 작동합니다.
Vagner

@ 4k4 url.path도 효과가있는 것 같습니다. 차이점이 뭐야?
Alex

2
@ Vagner : 렌더 배열에 캐시 태그 / 컨텍스트를 넣는 것도 좋지 않습니다. 데이터가있는 위치에 의존하기 때문입니다. 그리고 항상 거품이 생겨 위의 요소에 대해 걱정할 필요가 없습니다. Btw. 코드가 훌륭하고 캐싱 문제를 잘 설명합니다.
4k4

13

가장 쉬운 방법은 플러그인 / 블록 컨텍스트 시스템에 의존하는 것입니다.

내 대답을 참조하십시오 내가 현재 노드의 콘텐츠를 가져옵니다 블록을 어떻게해야합니까?

다음과 같이 블록 주석에 노드 컨텍스트 정의를 넣어야합니다.

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

그런 다음 다음과 같이 사용하십시오. $this->getContextValue('node')

이것에 대한 좋은 점은 Drupal이 캐싱을 처리한다는 것입니다. 자동으로. 기본 (및 코어에만 해당) 노드 컨텍스트가 현재 노드임을 알고 있기 때문입니다. 그리고 그것이 어디에서 왔는지 알기 때문에 캐시 컨텍스트와 캐시 태그가 자동으로 추가됩니다.

\Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()해당 getCacheTags()메소드를 통해 BlockBase / 블록 클래스는 그로부터 확장되어 해당 메소드를 상속합니다.


당신은 대체 \Drupal::routeMatch()->getParameter('node')와 함께 $this->getContextValue('node')당신은 한 줄의 코드로 전체 캐싱 문제를 해결? 큰!
4k4

1
지금까지 감사합니다! 전체 코드 예제를 제공 할 수 있습니까?
Alex

@Alex : 귀하의 질문을 편집했습니다. 오류가 발견되면 코드를 확인하고 변경하십시오.
4k4

@ 4k4 다른 솔루션도 작동하기 때문에 시도하지 않았습니다
Alex


7

에서 블록 플러그인 클래스를 파생시키는 경우 Drupal\Core\Block\BlockBase캐시 태그와 컨텍스트를 설정하는 두 가지 방법이 있습니다.

  • getCacheTags()
  • getCacheContexts()

예를 들어, Book 모듈 블록은 다음과 같은 방법을 구현합니다.

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

포럼 모듈 블록은 다음 코드를 사용합니다.

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

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

귀하의 경우 다음 코드를 사용합니다.

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

다음 방법을 사용하여 블록을 전혀 캐시 할 수 없게 만들 수도 있습니다 (피할지라도). 다른 경우에 유용 할 수 있습니다.

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

클래스 use Drupal\Core\Cache\Cache;를 사용하려는 경우 파일 맨 위에 추가해야합니다 Cache.


고맙지 만 / node / 2에서 블록은 캐시를 비운 후 처음에 node / 1을 방문했을 때 여전히 1을 출력합니다
Alex

2
활성화 된 모듈을 편집하는 경우 편집하기 전에 먼저 제거해야합니다. 캐시를 지우는 것만으로는 충분하지 않습니다.
kiamlaluno

알았지 만 maxAge 0을 추가하면 이상하게 작동합니다!
Alex

또한 블록 클래스가 BlockBase클래스를 부모 클래스로 사용합니까?
kiamlaluno

네 그것은 그것을 사용합니다
Alex

3

렌더 배열을 작성할 때는 항상 올바른 메타 데이터를 첨부하십시오.

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

이것은 블록 특정이 아니며 블록 플러그인 캐시 종속성 메소드 getCacheTags (), getCacheContext () 및 getCacheMaxAge ()는 대체되지 않습니다. 렌더 배열을 통해 전달할 수없는 추가 캐시 메타 데이터에만 사용해야합니다.

설명서를 참조하십시오.

"렌더 API에 렌더 배열의 캐시 가능성을 알려주는 것이 가장 중요합니다."

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

Drupal이 자동 자리 표시 자 및 지연 빌드를 통해 캐싱을 최적화 할 때 렌더 배열이 필요한 캐시 메타 데이터를 제공 할 것으로 예상하는이 예제를 참조하십시오. 사용자 컨텍스트가있는 사용자 정의 블록에서 사용자 특정 캐시 태그 설정 문제


블록 객체의 캐시를 설정할 수 있다고 생각하지 않습니다. '#markup'은 렌더 요소 객체 일 뿐이며 캐시 컨텍스트 나 태그를 설정할 이유가 없습니다. 캐시가 무효화 될 때 재 구축해야하는 블록 객체.
Vagner

#markup다른 렌더 요소와 동일하게 캐시 될 수 있습니다. 이 경우에는 마크 업이 아니라 캐시 된 블록이 문제입니다. 데이터베이스에서 노드가 변경되면 캐시 태그 만 무효화되므로 캐시 태그로는 해결할 수 없습니다.
4k4

@Vagner Block 객체의 캐시를 설정할 수 있습니다. BlockBase클래스는 필요한 방법도 있습니다.
kiamlaluno

1
나를 return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];위해 URL 캐싱마다 훌륭하게 작동합니다.
leymannx

1
예, @leymannx, 이것처럼 간단합니다. 이 스레드는 문제를 지나치게 생각하는 것 같습니다.
4k4

0

여기서 문제는 캐시 컨텍스트가 빌드 함수의 올바른 위치에 선언되지 않는다는 것입니다.

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

비 노드에서 해당 블록을 호출하면 빌드 함수가 빈 배열을 반환하므로이 블록에 대한 캐시 컨텍스트가 없으며 해당 동작은 drupal에 의해 캐시됩니다.이 블록의 표시가 제대로 무효화되거나 렌더링되지 않습니다.

해결책은 매번 캐시 컨텍스트를 사용하여 $ build를 초기화하는 것입니다.

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

나는이 대화에 늦었다는 것을 알고 있지만 아래 코드는 저에게 효과적이었습니다.

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

:)
Alex

0

hook_block_view_BASE_BLOCK_ID_alter를 구현해 보셨습니까?

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.