
Когда мы используем кеширование в Drupal, типичный подход — добавить user
в список контекстов: ['user']
. Это означает, что для каждого пользователя будет создана своя версия кеша. Иногда это оправдано. Но если доступ можно классифицировать в ограниченное число состояний — гораздо разумнее кешировать по этим состояниям.
В этой статье я покажу, как реализовать параметрический кеш-контекст на основе интерфейса CalculatedCacheContextInterface
. Мы создадим собственный cache context
, который учитывает тип доступа пользователя к конкретной ноде и используем его в hook_entity_view()
.
Пример: Кеширование тизера по виду доступа
Допустим, у нас есть статьи, доступ к которым может быть:
- открыт для всех,
- ограничен подпиской,
- доступен только после покупки.
Данные о наличии подписке и купленные статьи находятся в сущности user
.
Если кешировать по user
, база быстро раздуется. А если кешировать по состоянию доступа — мы сократим количество вариантов в десятки (или сотни) раз.
Реализация CalculatedCacheContextInterface
Drupal предоставляет пару способов создания собственных кеш-контекстов:
CacheContextInterface
— используется для обычных, глобальных контекстов (например,user
,language
).CalculatedCacheContextInterface
— расширяет первый и добавляет возможность передавать параметр в контекст, делая его параметрическим.
Нам подходит именно CalculatedCacheContextInterface
, потому что:
- Мы хотим кешировать не по пользователю, а по состоянию доступа к ноде.
- Это состояние зависит одновременно от пользователя и от конкретной ноды.
- Нам нужно передать ID ноды как параметр в контекст, что невозможно с обычным
CacheContextInterface
.
Создаём класс AccessToNodeCacheContext
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?php namespace Drupal\MODULE_NAME\Cache; use Drupal\Core\Cache\Context\CalculatedCacheContextInterface; use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Drupal\Core\Cache\CacheableMetadata; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\MODULE_NAME\AccessState; use Drupal\node\Entity\Node; class AccessToNodeCacheContext implements CalculatedCacheContextInterface { protected $currentUser; protected $accessState; public function __construct(AccountInterface $current_user, AccessState $access_state) { $this->currentUser = $current_user; $this->accessState = $access_state; } public static function getLabel() { return t('Access to node content'); } public function getContext($parameter = NULL) { $node = Node::load((int) $parameter); if ($node instanceof NodeInterface) { return $this->accessState->getAccessState($node); } return 'unknown'; } public function getCacheContextId() { return 'access_to_node'; } public function getCacheableMetadata($parameter = NULL) { return new CacheableMetadata(); } public static function create(ContainerInterface $container) { return new static( $container->get('current_user'), $container->get('MODULE_NAME.access_state') ); } } |
Регистрируем эту реализацию как сервис:
1 2 3 4 5 6 |
services: cache_context.access_to_node: class: Drupal\MODULE_NAME\Cache\AccessToNodeCacheContext arguments: ['@current_user', '@MODULE_NAME.access_state'] tags: - { name: cache.context } |
Использование кеш-контекста
Подключим параметрический контекст в hook_node_view()
:
1 2 3 4 5 6 7 8 9 10 11 |
function MODULE_NAME_node_view( array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void { if ((in_array($view_mode, ['teaser', 'full'])) && (in_array($entity->bundle(), ['article'])) ) { $build['#cache']['contexts'][] = 'access_to_node:' . $entity->id(); } } |
Здесь мы передаём ID ноды как параметр в кеш-контекст access_to_node
. Drupal при генерации кеша вызовет getContext()
и передаст туда этот параметр.
Как работает AccessState
Я не привожу полной реализации AccessState — это часть бизнес-логики, реализованная в виде сервиса с одним методом: getAccessState($node)
— возвращает тип доступа пользователя к указанной статье.
1 2 3 4 5 |
class AccessState { public function getAccessState(NodeInterface $node): string { // логика определения доступа: подписка, покупка, или публично } } |
Параметрический cache context
— мощный инструмент для контроля объёма кеша. Вместо тысяч кеш-ключей по user
, мы получаем 2–5 вариантов на каждую ноду — а это существенная оптимизация.
Если вы сталкиваетесь с подобной задачей — не спешите использовать ['user']
в контексте, подумайте, можно ли выразить различие через вычисляемый параметр.